/*
 * Copyright (C) 2015 HaiYang Li
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package com.landawn.abacus.util;

import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

import javax.sql.DataSource;

import com.landawn.abacus.DataSet;
import com.landawn.abacus.DirtyMarker;
import com.landawn.abacus.EntityId;
import com.landawn.abacus.IsolationLevel;
import com.landawn.abacus.annotation.Beta;
import com.landawn.abacus.annotation.LazyEvaluation;
import com.landawn.abacus.core.DirtyMarkerUtil;
import com.landawn.abacus.dao.CrudDao;
import com.landawn.abacus.exception.DuplicatedResultException;
import com.landawn.abacus.exception.UncheckedSQLException;
import com.landawn.abacus.logging.Logger;
import com.landawn.abacus.logging.LoggerFactory;
import com.landawn.abacus.type.Type;
import com.landawn.abacus.type.TypeFactory;
import com.landawn.abacus.util.JdbcUtil.BiRowMapper;
import com.landawn.abacus.util.JdbcUtil.ResultExtractor;
import com.landawn.abacus.util.SQLTransaction.CreatedBy;
import com.landawn.abacus.util.StringUtil.Strings;
import com.landawn.abacus.util.u.Nullable;
import com.landawn.abacus.util.u.Optional;
import com.landawn.abacus.util.u.OptionalBoolean;
import com.landawn.abacus.util.u.OptionalByte;
import com.landawn.abacus.util.u.OptionalChar;
import com.landawn.abacus.util.u.OptionalDouble;
import com.landawn.abacus.util.u.OptionalFloat;
import com.landawn.abacus.util.u.OptionalInt;
import com.landawn.abacus.util.u.OptionalLong;
import com.landawn.abacus.util.u.OptionalShort;
import com.landawn.abacus.util.function.BiConsumer;
import com.landawn.abacus.util.function.Function;
import com.landawn.abacus.util.function.Supplier;
import com.landawn.abacus.util.stream.ObjIteratorEx;
import com.landawn.abacus.util.stream.Stream;

/**
 * SQLExecutor is a simple sql/jdbc utility class. SQL is supported with different format: <br />
 *
 * <pre>
 *
 * <li> <code>INSERT INTO account (first_name, last_name, gui, last_update_time, create_time) VALUES (?,  ?,  ?,  ?,  ?)</code></li>
 * <li> <code>INSERT INTO account (first_name, last_name, gui, last_update_time, create_time) VALUES (#{firstName}, #{lastName}, #{gui}, #{lastUpdateTime}, #{createTime})</code></li>
 * <li> <code>INSERT INTO account (first_name, last_name, gui, last_update_time, create_time) VALUES (:firstName, :lastName, :gui, :lastUpdateTime, :createTime)</code></li>
 *
 * All these kinds of SQLs can be generated by <code>SQLBuilder</code> conveniently. Parameters with format of Object[]/List parameters are supported for parameterized SQL({@code id = ?}).
 * Parameters with format of Object[]/List/Map/Entity are supported for named parameterized SQL({@code id = :id}).
 * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
 * </pre>
 *
 * Here is sample of CRUD(create/read/update/delete):
 * <br />========================================================================
 * <pre>
 * <code>
 * static final DataSource dataSource = JdbcUtil.createDataSource(...);
 * static final SQLExecutor sqlExecutor = new SQLExecutor(dataSource);
 * ...
 * Account account = createAccount();
 *
 * // create
 * String sql_insert = NE.insert(GUI, FIRST_NAME, LAST_NAME, LAST_UPDATE_TIME, CREATE_TIME).into(Account.class).sql();
 * N.println(sql_insert);
 * sqlExecutor.insert(sql_insert, account);
 *
 * // read
 * String sql_selectByGUI = NE.selectFrom(Account.class, N.asSet(DEVICES)).where(L.eq(GUI, L.QME)).sql();
 * N.println(sql_selectByGUI);
 * Account dbAccount = sqlExecutor.findFirst(Account.class, sql_selectByGUI, account);
 * assertEquals(account.getFirstName(), dbAccount.getFirstName());
 *
 * // update
 * String sql_updateByLastName = NE.update(Account.class).set(FIRST_NAME).where(L.eq(LAST_NAME, L.QME)).sql();
 * N.println(sql_updateByLastName);
 * dbAccount.setFirstName("newFirstName");
 * sqlExecutor.update(sql_updateByLastName, dbAccount);
 *
 * // delete
 * String sql_deleteByFirstName = NE.deleteFrom(Account.class).where(L.eq(FIRST_NAME, L.QME)).sql();
 * N.println(sql_deleteByFirstName);
 * sqlExecutor.update(sql_deleteByFirstName, dbAccount);
 *
 * dbAccount = sqlExecutor.findFirst(Account.class, sql_selectByGUI, account);
 * assertNull(dbAccount);
 * </code>
 * </pre>
 * ========================================================================
 * <br />
 * <br />
 * If {@code conn} argument is null or not specified, {@code SQLExecutor} is responsible to get the connection from the
 * internal {@code DataSource}, start and commit/roll back transaction for batch operations if needed, and close the
 * connection finally. otherwise it's user's responsibility to do such jobs if {@code conn} is specified and not null. <br />
 * <br />
 *
 * Transaction can be started:
 * <pre>
 * <code>
 * final SQLTransaction tran = sqlExecutor.beginTransaction(IsolationLevel.READ_COMMITTED);
 *
 * try {
 *     // sqlExecutor.insert(...);
 *     // sqlExecutor.update(...);
 *     // sqlExecutor.query(...);
 *
 *     tran.commit();
 * } finally {
 *     // The connection will be automatically closed after the transaction is committed or rolled back.
 *     tran.rollbackIfNotCommitted();
 * }
 * </code>
 * </pre>
 *
 *
 * Spring Transaction is also supported and Integrated.
 * If a method of this class is called where a Spring transaction is started with the {@code DataSource} inside this {@code SQLExecutor}, without {@code Connection} parameter specified,
 * the {@code Connection} started the Spring Transaction will be used. Otherwise a {@code Connection} directly from the inside {@code DataSource}(Connection pool) will be borrowed and used.
 *
 *
 * SQLExecutor is tread-safe.<br /><br />
 *
 * @author Haiyang Li
 * @see <a href="./JdbcUtil.html">JdbcUtil</a>
 * @see com.landawn.abacus.annotation.ReadOnly
 * @see com.landawn.abacus.annotation.ReadOnlyId
 * @see com.landawn.abacus.annotation.NonUpdatable
 * @see com.landawn.abacus.annotation.Transient
 * @see com.landawn.abacus.annotation.Table
 * @see com.landawn.abacus.annotation.Column
 * @see com.landawn.abacus.condition.ConditionFactory
 * @see com.landawn.abacus.condition.ConditionFactory.CF
 * @see <a href="http://docs.oracle.com/javase/8/docs/api/java/sql/Connection.html">http://docs.oracle.com/javase/8/docs/api/java/sql/Connection.html</a>
 * @see <a href="http://docs.oracle.com/javase/8/docs/api/java/sql/Statement.html">http://docs.oracle.com/javase/8/docs/api/java/sql/Statement.html</a>
 * @see <a href="http://docs.oracle.com/javase/8/docs/api/java/sql/PreparedStatement.html">http://docs.oracle.com/javase/8/docs/api/java/sql/PreparedStatement.html</a>
 * @see <a href="http://docs.oracle.com/javase/8/docs/api/java/sql/ResultSet.html">http://docs.oracle.com/javase/8/docs/api/java/sql/ResultSet.html</a>
 * @since 0.8
 */
public final class SQLExecutor {

    /** The Constant logger. */
    private static final Logger logger = LoggerFactory.getLogger(SQLExecutor.class);

    /** The Constant ID. */
    static final String ID = "id";

    /** The Constant QUERY_WITH_DATA_SOURCE. */
    static final String QUERY_WITH_DATA_SOURCE = "queryWithDataSource";

    /** The Constant EXISTS_RESULT_SET_EXTRACTOR. */
    private static final ResultExtractor<Boolean> EXISTS_RESULT_SET_EXTRACTOR = new ResultExtractor<Boolean>() {
        @Override
        public Boolean apply(final ResultSet rs) throws SQLException {
            return rs.next();
        }
    };

    /** The Constant COUNT_RESULT_SET_EXTRACTOR. */
    private static final ResultExtractor<Integer> COUNT_RESULT_SET_EXTRACTOR = new ResultExtractor<Integer>() {
        @Override
        public Integer apply(final ResultSet rs) throws SQLException {
            int cnt = 0;

            while (rs.next()) {
                cnt++;
            }

            return cnt;
        }
    };

    /** The Constant SINGLE_BOOLEAN_EXTRACTOR. */
    private static final ResultExtractor<OptionalBoolean> SINGLE_BOOLEAN_EXTRACTOR = new ResultExtractor<OptionalBoolean>() {
        @Override
        public OptionalBoolean apply(final ResultSet rs) throws SQLException {
            if (rs.next()) {
                return OptionalBoolean.of(rs.getBoolean(1));
            }

            return OptionalBoolean.empty();
        }
    };

    /** The Constant charType. */
    private static final Type<Character> charType = TypeFactory.getType(char.class);

    /** The Constant SINGLE_CHAR_EXTRACTOR. */
    private static final ResultExtractor<OptionalChar> SINGLE_CHAR_EXTRACTOR = new ResultExtractor<OptionalChar>() {
        @Override
        public OptionalChar apply(final ResultSet rs) throws SQLException {
            if (rs.next()) {
                return OptionalChar.of(charType.get(rs, 1));
            }

            return OptionalChar.empty();
        }
    };

    /** The Constant SINGLE_BYTE_EXTRACTOR. */
    private static final ResultExtractor<OptionalByte> SINGLE_BYTE_EXTRACTOR = new ResultExtractor<OptionalByte>() {
        @Override
        public OptionalByte apply(final ResultSet rs) throws SQLException {
            if (rs.next()) {
                return OptionalByte.of(rs.getByte(1));
            }

            return OptionalByte.empty();
        }
    };

    /** The Constant SINGLE_SHORT_EXTRACTOR. */
    private static final ResultExtractor<OptionalShort> SINGLE_SHORT_EXTRACTOR = new ResultExtractor<OptionalShort>() {
        @Override
        public OptionalShort apply(final ResultSet rs) throws SQLException {
            if (rs.next()) {
                return OptionalShort.of(rs.getShort(1));
            }

            return OptionalShort.empty();
        }
    };

    /** The Constant SINGLE_INT_EXTRACTOR. */
    private static final ResultExtractor<OptionalInt> SINGLE_INT_EXTRACTOR = new ResultExtractor<OptionalInt>() {
        @Override
        public OptionalInt apply(final ResultSet rs) throws SQLException {
            if (rs.next()) {
                return OptionalInt.of(rs.getInt(1));
            }

            return OptionalInt.empty();
        }
    };

    /** The Constant SINGLE_LONG_EXTRACTOR. */
    private static final ResultExtractor<OptionalLong> SINGLE_LONG_EXTRACTOR = new ResultExtractor<OptionalLong>() {
        @Override
        public OptionalLong apply(final ResultSet rs) throws SQLException {
            if (rs.next()) {
                return OptionalLong.of(rs.getLong(1));
            }

            return OptionalLong.empty();
        }
    };

    /** The Constant SINGLE_FLOAT_EXTRACTOR. */
    private static final ResultExtractor<OptionalFloat> SINGLE_FLOAT_EXTRACTOR = new ResultExtractor<OptionalFloat>() {
        @Override
        public OptionalFloat apply(final ResultSet rs) throws SQLException {
            if (rs.next()) {
                return OptionalFloat.of(rs.getFloat(1));
            }

            return OptionalFloat.empty();
        }
    };

    /** The Constant SINGLE_DOUBLE_EXTRACTOR. */
    private static final ResultExtractor<OptionalDouble> SINGLE_DOUBLE_EXTRACTOR = new ResultExtractor<OptionalDouble>() {
        @Override
        public OptionalDouble apply(final ResultSet rs) throws SQLException {
            if (rs.next()) {
                return OptionalDouble.of(rs.getDouble(1));
            }

            return OptionalDouble.empty();
        }
    };

    /** The Constant SINGLE_BIG_DECIMAL_EXTRACTOR. */
    private static final ResultExtractor<Nullable<BigDecimal>> SINGLE_BIG_DECIMAL_EXTRACTOR = new ResultExtractor<Nullable<BigDecimal>>() {
        @Override
        public Nullable<BigDecimal> apply(final ResultSet rs) throws SQLException {
            if (rs.next()) {
                return Nullable.of(rs.getBigDecimal(1));
            }

            return Nullable.empty();
        }
    };

    /** The Constant SINGLE_STRING_EXTRACTOR. */
    private static final ResultExtractor<Nullable<String>> SINGLE_STRING_EXTRACTOR = new ResultExtractor<Nullable<String>>() {
        @Override
        public Nullable<String> apply(final ResultSet rs) throws SQLException {
            if (rs.next()) {
                return Nullable.of(rs.getString(1));
            }

            return Nullable.empty();
        }
    };

    /** The Constant SINGLE_DATE_EXTRACTOR. */
    private static final ResultExtractor<Nullable<Date>> SINGLE_DATE_EXTRACTOR = new ResultExtractor<Nullable<Date>>() {
        @Override
        public Nullable<Date> apply(final ResultSet rs) throws SQLException {
            if (rs.next()) {
                return Nullable.of(rs.getDate(1));
            }

            return Nullable.empty();
        }
    };

    /** The Constant SINGLE_TIME_EXTRACTOR. */
    private static final ResultExtractor<Nullable<Time>> SINGLE_TIME_EXTRACTOR = new ResultExtractor<Nullable<Time>>() {
        @Override
        public Nullable<Time> apply(final ResultSet rs) throws SQLException {
            if (rs.next()) {
                return Nullable.of(rs.getTime(1));
            }

            return Nullable.empty();
        }
    };

    /** The Constant SINGLE_TIMESTAMP_EXTRACTOR. */
    private static final ResultExtractor<Nullable<Timestamp>> SINGLE_TIMESTAMP_EXTRACTOR = new ResultExtractor<Nullable<Timestamp>>() {
        @Override
        public Nullable<Timestamp> apply(final ResultSet rs) throws SQLException {
            if (rs.next()) {
                return Nullable.of(rs.getTimestamp(1));
            }

            return Nullable.empty();
        }
    };

    /** The Constant factor. */
    private static final int factor = Math.min(Math.max(1, IOUtil.MAX_MEMORY_IN_MB / 1024), 8);

    /** The Constant CACHED_SQL_LENGTH. */
    private static final int CACHED_SQL_LENGTH = 1024 * factor;

    /** The Constant SQL_CACHE_SIZE. */
    private static final int SQL_CACHE_SIZE = 1000 * factor;

    /** The Constant _sqlColumnLabelPool. */
    private static final Map<String, ImmutableList<String>> _sqlColumnLabelPool = new ConcurrentHashMap<>();

    /** The table column name pool. */
    private final Map<String, ImmutableList<String>> _tableColumnNamePool = new ConcurrentHashMap<>();

    /** The ds. */
    private final DataSource _ds;

    /** The jdbc settings. */
    private final JdbcSettings _jdbcSettings;

    /** The sql mapper. */
    private final SQLMapper _sqlMapper;

    /** The default isolation level. */
    private final IsolationLevel _defaultIsolationLevel;

    /**
     * Instantiates a new SQL executor.
     *
     * @param dataSource
     * @see JdbcUtil#createDataSource(String)
     * @see JdbcUtil#createDataSource(java.io.InputStream)
     */
    public SQLExecutor(final DataSource dataSource) {
        this(dataSource, null);
    }

    /**
     * Instantiates a new SQL executor.
     *
     * @param dataSource
     * @param jdbcSettings
     * @see JdbcUtil#createDataSource(String)
     * @see JdbcUtil#createDataSource(java.io.InputStream)
     */
    public SQLExecutor(final DataSource dataSource, final JdbcSettings jdbcSettings) {
        this(dataSource, jdbcSettings, null);
    }

    /**
     * Instantiates a new SQL executor.
     *
     * @param dataSource
     * @param jdbcSettings
     * @param sqlMapper
     * @see JdbcUtil#createDataSource(String)
     * @see JdbcUtil#createDataSource(java.io.InputStream)
     */
    public SQLExecutor(final DataSource dataSource, final JdbcSettings jdbcSettings, final SQLMapper sqlMapper) {
        N.checkArgNotNull(dataSource, "dataSource");

        this._ds = dataSource;

        this._jdbcSettings = (jdbcSettings == null) ? JdbcSettings.create() : jdbcSettings.copy();

        if (_jdbcSettings.getBatchSize() == 0) {
            _jdbcSettings.setBatchSize(JdbcSettings.DEFAULT_BATCH_SIZE);
        }

        _jdbcSettings.freeze();

        this._sqlMapper = sqlMapper == null ? new SQLMapper() : sqlMapper;

        IsolationLevel defaultIsolationLevel = IsolationLevel.DEFAULT;
        final Connection conn = getConnection();

        try {
            defaultIsolationLevel = IsolationLevel.valueOf(conn.getTransactionIsolation());
        } catch (SQLException e) {
            throw new UncheckedSQLException(e);
        } finally {
            closeConnection(conn);
        }

        _defaultIsolationLevel = defaultIsolationLevel;
    }

    /**
     *
     * @param url
     * @param user
     * @param password
     * @return
     */
    @Beta
    public static SQLExecutor create(final String url, final String user, final String password) {
        return new SQLExecutor(JdbcUtil.createHikariDataSource(url, user, password));
    }

    /**
     *
     * @param driverClass
     * @param url
     * @param user
     * @param password
     * @return
     */
    @Beta
    public static SQLExecutor create(final DataSource dataSource) {
        return new SQLExecutor(dataSource);
    }

    /**
     *
     * @return
     */
    public DataSource dataSource() {
        return _ds;
    }

    /**
     *
     * @return
     * @deprecated should not update the returned {@code JdbcSettings}
     */
    @Deprecated
    @Beta
    public JdbcSettings jdbcSettings() {
        return _jdbcSettings;
    }

    /**
     *
     * @param <ID>
     * @param sql
     * @param parameters
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    @SafeVarargs
    public final <ID> ID insert(final String sql, final Object... parameters) throws UncheckedSQLException {
        return insert(sql, StatementSetter.DEFAULT, parameters);
    }

    /**
     *
     * @param <ID>
     * @param sql
     * @param statementSetter
     * @param parameters
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    @SafeVarargs
    public final <ID> ID insert(final String sql, final StatementSetter statementSetter, final Object... parameters) throws UncheckedSQLException {
        return insert(sql, statementSetter, null, parameters);
    }

    /**
     *
     * @param <ID>
     * @param sql
     * @param jdbcSettings
     * @param parameters
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    @SafeVarargs
    public final <ID> ID insert(final String sql, final JdbcSettings jdbcSettings, final Object... parameters) throws UncheckedSQLException {
        return insert(sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    /**
     *
     * @param <ID>
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    @SafeVarargs
    public final <ID> ID insert(final String sql, final StatementSetter statementSetter, final JdbcSettings jdbcSettings, final Object... parameters)
            throws UncheckedSQLException {
        return insert(sql, statementSetter, null, jdbcSettings, parameters);
    }

    /**
     *
     * @param <ID>
     * @param sql
     * @param statementSetter
     * @param autoGeneratedKeyExtractor
     * @param jdbcSettings
     * @param parameters
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    @SafeVarargs
    public final <ID> ID insert(final String sql, final StatementSetter statementSetter, final JdbcUtil.BiRowMapper<ID> autoGeneratedKeyExtractor,
            final JdbcSettings jdbcSettings, final Object... parameters) throws UncheckedSQLException {
        return insert(null, sql, statementSetter, autoGeneratedKeyExtractor, jdbcSettings, parameters);
    }

    /**
     *
     * @param <ID>
     * @param conn
     * @param sql
     * @param parameters
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    @SafeVarargs
    public final <ID> ID insert(final Connection conn, final String sql, final Object... parameters) throws UncheckedSQLException {
        return insert(conn, sql, StatementSetter.DEFAULT, parameters);
    }

    /**
     *
     * @param <ID>
     * @param conn
     * @param sql
     * @param statementSetter
     * @param parameters
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    @SafeVarargs
    public final <ID> ID insert(final Connection conn, final String sql, final StatementSetter statementSetter, final Object... parameters)
            throws UncheckedSQLException {
        return insert(conn, sql, statementSetter, null, parameters);
    }

    /**
     *
     * @param <ID>
     * @param conn
     * @param sql
     * @param jdbcSettings
     * @param parameters
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    public final <ID> ID insert(final Connection conn, final String sql, final JdbcSettings jdbcSettings, final Object... parameters)
            throws UncheckedSQLException {
        return insert(conn, sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    /**
     *
     * @param <ID>
     * @param conn
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    @SafeVarargs
    public final <ID> ID insert(final Connection conn, final String sql, StatementSetter statementSetter, JdbcSettings jdbcSettings, final Object... parameters)
            throws UncheckedSQLException {
        return insert(conn, sql, statementSetter, null, jdbcSettings, parameters);
    }

    /**
     *
     * @param <ID>
     * @param conn
     * @param sql
     * @param statementSetter
     * @param autoGeneratedKeyExtractor
     * @param jdbcSettings
     * @param parameters
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     * @see #batchInsert(Connection, String, StatementSetter, JdbcSettings, String, Object[])
     */
    @SuppressWarnings({ "unchecked", "deprecation" })
    @SafeVarargs
    public final <ID> ID insert(final Connection conn, final String sql, StatementSetter statementSetter, JdbcUtil.BiRowMapper<ID> autoGeneratedKeyExtractor,
            JdbcSettings jdbcSettings, final Object... parameters) throws UncheckedSQLException {
        final ParsedSql parsedSql = getParsedSql(sql);
        final boolean isEntityOrMapParameter = JdbcUtil.isEntityOrMapParameter(parsedSql, parameters);
        final boolean isEntity = isEntityOrMapParameter && ClassUtil.isEntity(parameters[0].getClass());
        final Collection<String> idPropNames = isEntity ? QueryUtil.getIdFieldNames(parameters[0].getClass()) : null;
        final boolean autoGeneratedKeys = isEntity == false || (N.notNullOrEmpty(idPropNames) && !parsedSql.getNamedParameters().containsAll(idPropNames));

        statementSetter = checkStatementSetter(parsedSql, statementSetter);
        jdbcSettings = checkJdbcSettings(jdbcSettings, parsedSql, _sqlMapper.getAttrs(sql));
        autoGeneratedKeyExtractor = checkGeneratedKeysExtractor(autoGeneratedKeyExtractor, jdbcSettings, parameters);

        DataSource ds = null;
        Connection localConn = null;
        Object id = null;
        PreparedStatement stmt = null;

        try {
            ds = getDataSource(parsedSql.getParameterizedSql(), parameters, jdbcSettings);

            localConn = getConnection(conn, ds, jdbcSettings, SQLOperation.INSERT);

            stmt = prepareStatement(ds, localConn, parsedSql, statementSetter, jdbcSettings, autoGeneratedKeys, false, parameters);

            id = executeInsert(parsedSql, stmt, autoGeneratedKeyExtractor, autoGeneratedKeys);
        } catch (SQLException e) {
            String msg = ExceptionUtil.getMessage(e) + ". [SQL] " + parsedSql.sql();
            throw new UncheckedSQLException(msg, e);
        } finally {
            close(stmt);
            close(localConn, conn, ds);
        }

        if (isEntityOrMapParameter && isEntity) {
            final Object entity = parameters[0];

            if (id == null) {
                id = getIdGetter(entity).apply(entity);
            } else {
                getIdSetter(entity).accept(id, entity);
            }

            if (entity instanceof DirtyMarker) {
                DirtyMarkerUtil.dirtyPropNames((DirtyMarker) parameters[0]).clear();
            }
        }

        return (ID) id;
    }

    static <ID> JdbcUtil.BiRowMapper<ID> checkGeneratedKeysExtractor(JdbcUtil.BiRowMapper<ID> autoGeneratedKeyExtractor, final JdbcSettings jdbcSettings,
            final Object... parameters) {
        if ((autoGeneratedKeyExtractor == null || autoGeneratedKeyExtractor == JdbcUtil.SINGLE_BI_GENERATED_KEY_EXTRACTOR
                || autoGeneratedKeyExtractor == JdbcUtil.MULTI_BI_GENERATED_KEY_EXTRACTOR) //
                && N.notNullOrEmpty(parameters) && parameters.length == 1 && parameters[0] != null && ClassUtil.isEntity(parameters[0].getClass())) {
            return (JdbcUtil.BiRowMapper<ID>) JdbcUtil.getIdGeneratorGetterSetter(CrudDao.class, parameters[0].getClass(),
                    NamingPolicy.LOWER_CASE_WITH_UNDERSCORE, EntityId.class)._1;
        } else if (autoGeneratedKeyExtractor == null) {
            if (jdbcSettings != null && ((N.notNullOrEmpty(jdbcSettings.getReturnedColumnIndexes()) && jdbcSettings.getReturnedColumnIndexes().length > 1)
                    || (N.notNullOrEmpty(jdbcSettings.getReturnedColumnNames()) && jdbcSettings.getReturnedColumnNames().length > 1))) {
                return (JdbcUtil.BiRowMapper<ID>) JdbcUtil.MULTI_BI_GENERATED_KEY_EXTRACTOR;
            } else {
                return (JdbcUtil.BiRowMapper<ID>) JdbcUtil.SINGLE_BI_GENERATED_KEY_EXTRACTOR;
            }
        }

        return autoGeneratedKeyExtractor;
    }

    static <ID> Function<Object, ID> getIdGetter(final Object entity) {
        return (Function<Object, ID>) JdbcUtil.getIdGeneratorGetterSetter(CrudDao.class, entity == null ? null : entity.getClass(),
                NamingPolicy.LOWER_CASE_WITH_UNDERSCORE, EntityId.class)._2;
    }

    static <ID> BiConsumer<ID, Object> getIdSetter(final Object entity) {
        return (BiConsumer<ID, Object>) JdbcUtil.getIdGeneratorGetterSetter(CrudDao.class, entity == null ? null : entity.getClass(),
                NamingPolicy.LOWER_CASE_WITH_UNDERSCORE, EntityId.class)._3;
    }

    protected <ID> ID executeInsert(@SuppressWarnings("unused") final ParsedSql parsedSql, final PreparedStatement stmt,
            final JdbcUtil.BiRowMapper<ID> autoGeneratedKeyExtractor, final boolean autoGeneratedKeys) throws SQLException {
        JdbcUtil.executeUpdate(stmt);

        ID id = null;

        if (autoGeneratedKeys) {
            ResultSet rs = null;

            try {
                rs = stmt.getGeneratedKeys();
                id = rs.next() ? autoGeneratedKeyExtractor.apply(rs, JdbcUtil.getColumnLabelList(rs)) : null;
            } catch (SQLException e) {
                logger.error("Failed to retrieve the auto-generated Ids", e);
            } finally {
                close(rs);
            }
        }

        return id;
    }

    /**
     *
     * @param <ID>
     * @param sql
     * @param parametersList
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    public <ID> List<ID> batchInsert(final String sql, final List<?> parametersList) throws UncheckedSQLException {
        return batchInsert(sql, StatementSetter.DEFAULT, parametersList);
    }

    /**
     *
     * @param <ID>
     * @param sql
     * @param statementSetter
     * @param parametersList
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    public <ID> List<ID> batchInsert(final String sql, final StatementSetter statementSetter, final List<?> parametersList) throws UncheckedSQLException {
        return batchInsert(sql, statementSetter, null, parametersList);
    }

    /**
     *
     * @param <ID>
     * @param sql
     * @param jdbcSettings
     * @param parametersList
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    public <ID> List<ID> batchInsert(final String sql, final JdbcSettings jdbcSettings, final List<?> parametersList) throws UncheckedSQLException {
        return batchInsert(sql, StatementSetter.DEFAULT, jdbcSettings, parametersList);
    }

    /**
     *
     * @param <ID>
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parametersList
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    public <ID> List<ID> batchInsert(final String sql, final StatementSetter statementSetter, final JdbcSettings jdbcSettings, final List<?> parametersList)
            throws UncheckedSQLException {
        return batchInsert(sql, statementSetter, null, jdbcSettings, parametersList);
    }

    /**
     *
     * @param <ID>
     * @param sql
     * @param statementSetter
     * @param autoGeneratedKeyExtractor
     * @param jdbcSettings
     * @param parametersList
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    public <ID> List<ID> batchInsert(final String sql, final StatementSetter statementSetter, final JdbcUtil.BiRowMapper<ID> autoGeneratedKeyExtractor,
            final JdbcSettings jdbcSettings, final List<?> parametersList) throws UncheckedSQLException {
        return batchInsert(null, sql, statementSetter, autoGeneratedKeyExtractor, jdbcSettings, parametersList);
    }

    /**
     *
     * @param <ID>
     * @param conn
     * @param sql
     * @param parametersList
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    public <ID> List<ID> batchInsert(final Connection conn, final String sql, final List<?> parametersList) throws UncheckedSQLException {
        return batchInsert(conn, sql, StatementSetter.DEFAULT, parametersList);
    }

    /**
     *
     * @param <ID>
     * @param conn
     * @param sql
     * @param statementSetter
     * @param parametersList
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    public <ID> List<ID> batchInsert(final Connection conn, final String sql, final StatementSetter statementSetter, final List<?> parametersList)
            throws UncheckedSQLException {
        return batchInsert(conn, sql, statementSetter, null, parametersList);
    }

    /**
     *
     * @param <ID>
     * @param conn
     * @param sql
     * @param jdbcSettings
     * @param parametersList
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    public <ID> List<ID> batchInsert(final Connection conn, final String sql, final JdbcSettings jdbcSettings, final List<?> parametersList)
            throws UncheckedSQLException {
        return batchInsert(conn, sql, StatementSetter.DEFAULT, jdbcSettings, parametersList);
    }

    /**
     *
     * @param <ID>
     * @param conn
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parametersList
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    public <ID> List<ID> batchInsert(final Connection conn, final String sql, StatementSetter statementSetter, JdbcSettings jdbcSettings,
            final List<?> parametersList) throws UncheckedSQLException {
        return batchInsert(conn, sql, statementSetter, null, jdbcSettings, parametersList);
    }

    /**
     *
     * @param <ID>
     * @param conn
     * @param sql
     * @param statementSetter
     * @param autoGeneratedKeyExtractor
     * @param jdbcSettings
     * @param parametersList
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    @SuppressWarnings({ "deprecation", "null" })
    public <ID> List<ID> batchInsert(final Connection conn, final String sql, StatementSetter statementSetter,
            JdbcUtil.BiRowMapper<ID> autoGeneratedKeyExtractor, JdbcSettings jdbcSettings, final List<?> parametersList) throws UncheckedSQLException {
        N.checkArgNotNullOrEmpty(parametersList, "parametersList");

        final ParsedSql parsedSql = getParsedSql(sql);
        final Object parameters_0 = parametersList.get(0);
        final boolean isEntityOrMapParameter = JdbcUtil.isEntityOrMapParameter(parsedSql, parameters_0);
        final boolean isEntity = isEntityOrMapParameter && ClassUtil.isEntity(parameters_0.getClass());
        final Collection<String> idPropNames = isEntity ? QueryUtil.getIdFieldNames(parameters_0.getClass()) : null;
        final boolean autoGeneratedKeys = isEntity == false || (N.notNullOrEmpty(idPropNames) && !parsedSql.getNamedParameters().containsAll(idPropNames));

        statementSetter = checkStatementSetter(parsedSql, statementSetter);
        jdbcSettings = checkJdbcSettings(jdbcSettings, parsedSql, _sqlMapper.getAttrs(sql));
        autoGeneratedKeyExtractor = checkGeneratedKeysExtractor(autoGeneratedKeyExtractor, jdbcSettings, parametersList.get(0));

        final int len = parametersList.size();
        final int batchSize = getBatchSize(jdbcSettings);

        List<ID> ids = new ArrayList<>(len);

        DataSource ds = null;
        Connection localConn = null;
        PreparedStatement stmt = null;
        int originalIsolationLevel = 0;
        boolean autoCommit = true;
        final Object[] parameters = new Object[1];

        try {
            ds = getDataSource(parsedSql.getParameterizedSql(), parametersList, jdbcSettings);

            localConn = getConnection(conn, ds, jdbcSettings, SQLOperation.INSERT);

            try {
                originalIsolationLevel = localConn.getTransactionIsolation();
                autoCommit = localConn.getAutoCommit();
            } catch (SQLException e) {
                close(localConn, conn, ds);
                throw new UncheckedSQLException(e);
            }

            if ((conn == null) && (len > batchSize)) {
                localConn.setAutoCommit(false);

                setIsolationLevel(jdbcSettings, localConn);
            }

            stmt = prepareStatement(ds, localConn, parsedSql, statementSetter, jdbcSettings, autoGeneratedKeys, true, parametersList);

            if (len <= batchSize) {
                for (int i = 0; i < len; i++) {
                    parameters[0] = parametersList.get(i);

                    statementSetter.accept(parsedSql, stmt, parameters);
                    stmt.addBatch();
                }

                executeBatchInsert(ids, parsedSql, stmt, autoGeneratedKeyExtractor, autoGeneratedKeys);
            } else {
                int num = 0;

                for (int i = 0; i < len; i++) {
                    parameters[0] = parametersList.get(i);

                    statementSetter.accept(parsedSql, stmt, parameters);
                    stmt.addBatch();
                    num++;

                    if ((num % batchSize) == 0) {
                        executeBatchInsert(ids, parsedSql, stmt, autoGeneratedKeyExtractor, autoGeneratedKeys);
                    }
                }

                if ((num % batchSize) > 0) {
                    executeBatchInsert(ids, parsedSql, stmt, autoGeneratedKeyExtractor, autoGeneratedKeys);
                }
            }

            if ((conn == null) && (len > batchSize) && autoCommit == true) {
                localConn.commit();
            }
        } catch (SQLException e) {
            if ((conn == null) && (len > batchSize) && autoCommit == true) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Trying to roll back ...");
                }

                try {
                    localConn.rollback();

                    if (logger.isWarnEnabled()) {
                        logger.warn("succeeded to roll back");
                    }
                } catch (SQLException e1) {
                    logger.error("Failed to roll back", e1);
                }
            }

            String msg = ExceptionUtil.getMessage(e) + ". [SQL] " + parsedSql.sql();
            throw new UncheckedSQLException(msg, e);
        } finally {
            if ((conn == null) && (len > batchSize)) {
                try {
                    localConn.setAutoCommit(autoCommit);
                    localConn.setTransactionIsolation(originalIsolationLevel);
                } catch (SQLException e) {
                    logger.error("Failed to reset AutoCommit", e);
                }
            }

            close(stmt);
            close(localConn, conn, ds);
        }

        if (N.notNullOrEmpty(ids) && Stream.of(ids).allMatch(Fn.isNull())) {
            ids = new ArrayList<>();
        }

        if (N.notNullOrEmpty(ids) && ids.size() != parametersList.size()) {
            if (logger.isWarnEnabled()) {
                logger.warn("The size of returned id list: {} is different from the size of input parameter list: {}", ids.size(), parametersList.size());
            }
        }

        if (parametersList.get(0) != null && JdbcUtil.isEntityOrMapParameter(parsedSql, parametersList.get(0))
                && ClassUtil.isEntity(parametersList.get(0).getClass())) {
            final Object entity = parametersList.get(0);

            if (N.isNullOrEmpty(ids)) {
                final Function<Object, ID> idGetter = getIdGetter(entity);

                ids = Stream.of(parametersList).map(idGetter).toList();
            } else {
                final BiConsumer<ID, Object> idSetter = getIdSetter(entity);

                if (ids.size() == len) {
                    for (int i = 0; i < len; i++) {
                        idSetter.accept(ids.get(i), parametersList.get(i));
                    }
                } else {
                    if (logger.isWarnEnabled()) {
                        logger.warn(
                                "Failed to set the returned id property to entity/map. because the size of returned key not equals the lenght of the input arrray");
                    }
                }
            }

            if (entity instanceof DirtyMarker) {
                for (Object e : parametersList) {
                    DirtyMarkerUtil.dirtyPropNames((DirtyMarker) e).clear();
                }
            }
        }

        return ids;
    }

    /**
     * Sets the isolation level.
     *
     * @param jdbcSettings
     * @param localConn
     * @throws SQLException the SQL exception
     */
    private void setIsolationLevel(JdbcSettings jdbcSettings, Connection localConn) throws SQLException {
        final int isolationLevel = jdbcSettings.getIsolationLevel() == null || jdbcSettings.getIsolationLevel() == IsolationLevel.DEFAULT
                ? _defaultIsolationLevel.intValue()
                : jdbcSettings.getIsolationLevel().intValue();

        if (isolationLevel == localConn.getTransactionIsolation()) {
            // ignore.
        } else {
            localConn.setTransactionIsolation(isolationLevel);
        }
    }

    /**
     * Execute batch insert.
     *
     * @param <ID>
     * @param resultIdList
     * @param parsedSql
     * @param stmt
     * @param autoGeneratedKeyExtractor
     * @param autoGeneratedKeys
     * @throws SQLException the SQL exception
     */
    protected <ID> void executeBatchInsert(final List<ID> resultIdList, @SuppressWarnings("unused") final ParsedSql parsedSql, final PreparedStatement stmt,
            final JdbcUtil.BiRowMapper<ID> autoGeneratedKeyExtractor, final boolean autoGeneratedKeys) throws SQLException {
        executeBatch(stmt);

        if (autoGeneratedKeys) {
            ResultSet rs = null;

            try {
                rs = stmt.getGeneratedKeys();
                final List<String> columnLabels = JdbcUtil.getColumnLabelList(rs);

                while (rs.next()) {
                    resultIdList.add(autoGeneratedKeyExtractor.apply(rs, columnLabels));
                }
            } catch (SQLException e) {
                logger.error("Failed to retrieve the auto-generated Ids", e);
            } finally {
                close(rs);
            }
        }
    }

    @SuppressWarnings("static-method")
    private int[] executeBatch(final PreparedStatement stmt) throws SQLException {
        return JdbcUtil.executeBatch(stmt);
    }

    /**
     *
     * @param sql
     * @param parameters
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    @SafeVarargs
    public final int update(final String sql, final Object... parameters) throws UncheckedSQLException {
        return update(sql, StatementSetter.DEFAULT, parameters);
    }

    /**
     *
     * @param sql
     * @param statementSetter
     * @param parameters
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    @SafeVarargs
    public final int update(final String sql, final StatementSetter statementSetter, final Object... parameters) throws UncheckedSQLException {
        return update(sql, statementSetter, null, parameters);
    }

    /**
     *
     * @param sql
     * @param jdbcSettings
     * @param parameters
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    @SafeVarargs
    public final int update(final String sql, final JdbcSettings jdbcSettings, final Object... parameters) throws UncheckedSQLException {
        return update(sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    /**
     *
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    @SafeVarargs
    public final int update(final String sql, final StatementSetter statementSetter, final JdbcSettings jdbcSettings, final Object... parameters)
            throws UncheckedSQLException {
        return update(null, sql, statementSetter, jdbcSettings, parameters);
    }

    /**
     *
     * @param conn
     * @param sql
     * @param parameters
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    @SafeVarargs
    public final int update(final Connection conn, final String sql, final Object... parameters) throws UncheckedSQLException {
        return update(conn, sql, StatementSetter.DEFAULT, parameters);
    }

    /**
     *
     * @param conn
     * @param sql
     * @param statementSetter
     * @param parameters
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    @SafeVarargs
    public final int update(final Connection conn, final String sql, final StatementSetter statementSetter, final Object... parameters)
            throws UncheckedSQLException {
        return update(conn, sql, statementSetter, null, parameters);
    }

    /**
     *
     * @param conn
     * @param sql
     * @param jdbcSettings
     * @param parameters
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    @SafeVarargs
    public final int update(final Connection conn, final String sql, final JdbcSettings jdbcSettings, final Object... parameters) throws UncheckedSQLException {
        return update(conn, sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    /**
     *
     * @param conn
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     * @see #batchUpdate(Connection, String, StatementSetter, JdbcSettings, Object[])
     */
    @SafeVarargs
    public final int update(final Connection conn, final String sql, StatementSetter statementSetter, JdbcSettings jdbcSettings, final Object... parameters)
            throws UncheckedSQLException {
        final ParsedSql parsedSql = getParsedSql(sql);
        statementSetter = checkStatementSetter(parsedSql, statementSetter);
        jdbcSettings = checkJdbcSettings(jdbcSettings, parsedSql, _sqlMapper.getAttrs(sql));

        DataSource ds = null;
        Connection localConn = null;
        PreparedStatement stmt = null;

        try {
            ds = getDataSource(parsedSql.getParameterizedSql(), parameters, jdbcSettings);

            localConn = getConnection(conn, ds, jdbcSettings, SQLOperation.UPDATE);

            stmt = prepareStatement(ds, localConn, parsedSql, statementSetter, jdbcSettings, false, false, parameters);

            final int result = executeUpdate(parsedSql, stmt);

            if (JdbcUtil.isEntityOrMapParameter(parsedSql, parameters)) {
                if (parameters[0] instanceof DirtyMarker) {
                    DirtyMarkerUtil.markDirty((DirtyMarker) parameters[0], parsedSql.getNamedParameters(), false);
                }
            }

            return result;
        } catch (SQLException e) {
            String msg = ExceptionUtil.getErrorMessage(e, true) + ". [SQL] " + parsedSql.sql();
            throw new UncheckedSQLException(msg, e);
        } finally {
            close(stmt);
            close(localConn, conn, ds);
        }
    }

    /**
     *
     * @param parsedSql
     * @param stmt
     * @return
     * @throws SQLException the SQL exception
     */
    @SuppressWarnings("static-method")
    protected int executeUpdate(@SuppressWarnings("unused") final ParsedSql parsedSql, final PreparedStatement stmt) throws SQLException {
        return JdbcUtil.executeUpdate(stmt);
    }

    /**
     *
     * @param sql
     * @param parametersList
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    public int batchUpdate(final String sql, final List<?> parametersList) throws UncheckedSQLException {
        return batchUpdate(sql, StatementSetter.DEFAULT, parametersList);
    }

    /**
     *
     * @param sql
     * @param statementSetter
     * @param parametersList
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    public int batchUpdate(final String sql, final StatementSetter statementSetter, final List<?> parametersList) throws UncheckedSQLException {
        return batchUpdate(sql, statementSetter, null, parametersList);
    }

    /**
     *
     * @param sql
     * @param jdbcSettings
     * @param parametersList
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    public int batchUpdate(final String sql, final JdbcSettings jdbcSettings, final List<?> parametersList) throws UncheckedSQLException {
        return batchUpdate(sql, StatementSetter.DEFAULT, jdbcSettings, parametersList);
    }

    /**
     *
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parametersList
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    public int batchUpdate(final String sql, final StatementSetter statementSetter, final JdbcSettings jdbcSettings, final List<?> parametersList)
            throws UncheckedSQLException {
        return batchUpdate(null, sql, statementSetter, jdbcSettings, parametersList);
    }

    /**
     *
     * @param conn
     * @param sql
     * @param parametersList
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    public int batchUpdate(final Connection conn, final String sql, final List<?> parametersList) throws UncheckedSQLException {
        return batchUpdate(conn, sql, StatementSetter.DEFAULT, parametersList);
    }

    /**
     *
     * @param conn
     * @param sql
     * @param statementSetter
     * @param parametersList
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    public int batchUpdate(final Connection conn, final String sql, final StatementSetter statementSetter, final List<?> parametersList)
            throws UncheckedSQLException {
        return batchUpdate(conn, sql, statementSetter, null, parametersList);
    }

    /**
     *
     * @param conn
     * @param sql
     * @param jdbcSettings
     * @param parametersList
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    public int batchUpdate(final Connection conn, final String sql, final JdbcSettings jdbcSettings, final List<?> parametersList)
            throws UncheckedSQLException {
        return batchUpdate(conn, sql, StatementSetter.DEFAULT, jdbcSettings, parametersList);
    }

    /**
     *
     * @param conn
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parametersList
     * @return
     * @throws UncheckedSQLException the unchecked SQL exception
     * @see #batchUpdate(Connection, String, StatementSetter, JdbcSettings, Object[])
     */
    @SuppressWarnings("null")
    public int batchUpdate(final Connection conn, final String sql, StatementSetter statementSetter, JdbcSettings jdbcSettings, final List<?> parametersList)
            throws UncheckedSQLException {
        final ParsedSql parsedSql = getParsedSql(sql);
        statementSetter = checkStatementSetter(parsedSql, statementSetter);
        jdbcSettings = checkJdbcSettings(jdbcSettings, parsedSql, _sqlMapper.getAttrs(sql));

        final int len = parametersList.size();
        final int batchSize = getBatchSize(jdbcSettings);

        DataSource ds = null;
        Connection localConn = null;
        PreparedStatement stmt = null;
        int originalIsolationLevel = 0;
        boolean autoCommit = true;

        try {
            ds = getDataSource(parsedSql.getParameterizedSql(), parametersList, jdbcSettings);

            localConn = getConnection(conn, ds, jdbcSettings, SQLOperation.UPDATE);

            try {
                originalIsolationLevel = localConn.getTransactionIsolation();
                autoCommit = localConn.getAutoCommit();
            } catch (SQLException e) {
                close(localConn, conn, ds);
                throw new UncheckedSQLException(e);
            }

            if ((conn == null) && (len > batchSize)) {
                localConn.setAutoCommit(false);

                setIsolationLevel(jdbcSettings, localConn);
            }

            stmt = prepareStatement(ds, localConn, parsedSql, statementSetter, jdbcSettings, false, true, parametersList);

            int result = 0;
            final Object[] parameters = new Object[1];

            if (len <= batchSize) {
                for (int i = 0; i < len; i++) {
                    parameters[0] = parametersList.get(i);

                    statementSetter.accept(parsedSql, stmt, parameters);
                    stmt.addBatch();
                }

                result += executeBatchUpdate(parsedSql, stmt);
            } else {
                int num = 0;

                for (int i = 0; i < len; i++) {
                    parameters[0] = parametersList.get(i);

                    statementSetter.accept(parsedSql, stmt, parameters);
                    stmt.addBatch();
                    num++;

                    if ((num % batchSize) == 0) {
                        result += executeBatchUpdate(parsedSql, stmt);
                    }
                }

                if ((num % batchSize) > 0) {
                    result += executeBatchUpdate(parsedSql, stmt);
                }
            }

            if ((conn == null) && (len > batchSize) && autoCommit == true) {
                localConn.commit();
            }

            if (N.firstOrNullIfEmpty(parametersList) instanceof DirtyMarker) {
                for (Object e : parametersList) {
                    DirtyMarkerUtil.markDirty((DirtyMarker) e, parsedSql.getNamedParameters(), false);
                }
            }

            return result;
        } catch (SQLException e) {
            if ((conn == null) && (len > batchSize) && autoCommit == true) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Trying to roll back ...");
                }

                try {
                    localConn.rollback();

                    if (logger.isWarnEnabled()) {
                        logger.warn("succeeded to roll back");
                    }
                } catch (SQLException e1) {
                    logger.error("Failed to roll back", e1);
                }
            }

            String msg = ExceptionUtil.getErrorMessage(e, true) + ". [SQL] " + parsedSql.sql();
            throw new UncheckedSQLException(msg, e);
        } finally {
            if ((conn == null) && (len > batchSize)) {
                try {
                    localConn.setAutoCommit(autoCommit);
                    localConn.setTransactionIsolation(originalIsolationLevel);
                } catch (SQLException e) {
                    logger.error("Failed to reset AutoCommit", e);
                }
            }

            close(stmt);
            close(localConn, conn, ds);
        }
    }

    /**
     * Execute batch update.
     *
     * @param parsedSql
     * @param stmt
     * @return
     * @throws SQLException the SQL exception
     */
    protected int executeBatchUpdate(@SuppressWarnings("unused") final ParsedSql parsedSql, final PreparedStatement stmt) throws SQLException {
        final int[] results = executeBatch(stmt);

        if ((results == null) || (results.length == 0)) {
            return 0;
        }

        int sum = 0;

        for (int i = 0; i < results.length; i++) {
            sum += results[i];
        }

        return sum;
    }

    //    // mess up. To uncomment this method, also need to modify getNamingPolicy/setNamingPolicy in JdbcSettings.
    //    int update(final EntityId entityId, final Map<String, Object> props) {
    //        return update(null, entityId, props);
    //    }
    //
    //    // mess up. To uncomment this method, also need to modify getNamingPolicy/setNamingPolicy in JdbcSettings.
    //    int update(final Connection conn, final EntityId entityId, final Map<String, Object> props) {
    //        final Pair2 pair = generateUpdateSQL(entityId, props);
    //
    //        return update(conn, sp.sql, sp.parameters);
    //    }
    //
    //    private Pair2 generateUpdateSQL(final EntityId entityId, final Map<String, Object> props) {
    //        final Condition cond = EntityManagerUtil.entityId2Condition(entityId);
    //        final NamingPolicy namingPolicy = _jdbcSettings.getNamingPolicy();
    //
    //        if (namingPolicy == null) {
    //            return NE.update(entityId.entityName()).set(props).where(cond).pair();
    //        }
    //
    //        switch (namingPolicy) {
    //            case LOWER_CASE_WITH_UNDERSCORE: {
    //                return NE.update(entityId.entityName()).set(props).where(cond).pair();
    //            }
    //
    //            case UPPER_CASE_WITH_UNDERSCORE: {
    //                return NE2.update(entityId.entityName()).set(props).where(cond).pair();
    //            }
    //
    //            case CAMEL_CASE: {
    //                return NE3.update(entityId.entityName()).set(props).where(cond).pair();
    //            }
    //
    //            default:
    //                throw new IllegalArgumentException("Unsupported naming policy");
    //        }
    //    }
    //
    //    // mess up. To uncomment this method, also need to modify getNamingPolicy/setNamingPolicy in JdbcSettings.
    //    int delete(final EntityId entityId) {
    //        return delete(null, entityId);
    //    }
    //
    //    // mess up. To uncomment this method, also need to modify getNamingPolicy/setNamingPolicy in JdbcSettings.
    //    int delete(final Connection conn, final EntityId entityId) {
    //        final Pair2 pair = generateDeleteSQL(entityId);
    //
    //        return update(conn, sp.sql, sp.parameters);
    //    }
    //
    //    private Pair2 generateDeleteSQL(final EntityId entityId) {
    //        final Condition cond = EntityManagerUtil.entityId2Condition(entityId);
    //        final NamingPolicy namingPolicy = _jdbcSettings.getNamingPolicy();
    //
    //        if (namingPolicy == null) {
    //            return NE.deleteFrom(entityId.entityName()).where(cond).pair();
    //        }
    //
    //        switch (namingPolicy) {
    //            case LOWER_CASE_WITH_UNDERSCORE: {
    //                return NE.deleteFrom(entityId.entityName()).where(cond).pair();
    //            }
    //
    //            case UPPER_CASE_WITH_UNDERSCORE: {
    //                return NE2.deleteFrom(entityId.entityName()).where(cond).pair();
    //            }
    //
    //            case CAMEL_CASE: {
    //                return NE3.deleteFrom(entityId.entityName()).where(cond).pair();
    //            }
    //
    //            default:
    //                throw new IllegalArgumentException("Unsupported naming policy");
    //        }
    //    }
    //
    //    // mess up. To uncomment this method, also need to modify getNamingPolicy/setNamingPolicy in JdbcSettings.
    //    boolean exists(final EntityId entityId) {
    //        return exists(null, entityId);
    //    }
    //
    //    // mess up. To uncomment this method, also need to modify getNamingPolicy/setNamingPolicy in JdbcSettings.
    //    boolean exists(final Connection conn, final EntityId entityId) {
    //        final Pair2 pair = generateQuerySQL(entityId, NE._1_list);
    //
    //        return query(conn, sp.sql, StatementSetter.DEFAULT, EXISTS_RESULT_SET_EXTRACTOR, null, sp.parameters);
    //    }

    /**
     *
     * @param sql
     * @param parameters
     * @return true, if successful
     */
    @SafeVarargs
    public final boolean exists(final String sql, final Object... parameters) {
        return exists(null, sql, parameters);
    }

    /**
     *
     * @param conn
     * @param sql
     * @param parameters
     * @return true, if successful
     */
    @SafeVarargs
    public final boolean exists(final Connection conn, final String sql, final Object... parameters) {
        return query(conn, sql, StatementSetter.DEFAULT, EXISTS_RESULT_SET_EXTRACTOR, null, parameters);
    }

    /**
    *
    * @param sql
    * @param parameters
    * @return true, if successful
    */
    @Beta
    @SafeVarargs
    public final boolean notExists(final String sql, final Object... parameters) {
        return !exists(sql, parameters);
    }

    /**
    *
    * @param conn
    * @param sql
    * @param parameters
    * @return true, if successful
    */
    @Beta
    @SafeVarargs
    public final boolean notExists(final Connection conn, final String sql, final Object... parameters) {
        return !exists(conn, sql, parameters);
    }

    /**
     *
     * @param sql
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @deprecated may be misused and it's inefficient.
     */
    @Deprecated
    @SafeVarargs
    final int count(final String sql, final Object... parameters) {
        return count(null, sql, parameters);
    }

    /**
     *
     * @param conn
     * @param sql
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @deprecated may be misused and it's inefficient.
     */
    @Deprecated
    @SafeVarargs
    final int count(final Connection conn, final String sql, final Object... parameters) {
        return query(conn, sql, StatementSetter.DEFAULT, COUNT_RESULT_SET_EXTRACTOR, null, parameters);
    }

    /**
     *
     * @param <T>
     * @param targetClass
     * @param sql
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> Optional<T> get(final Class<T> targetClass, final String sql, final Object... parameters) throws DuplicatedResultException {
        return Optional.ofNullable(gett(targetClass, sql, parameters));
    }

    /**
     *
     * @param <T>
     * @param targetClass
     * @param sql
     * @param statementSetter
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> Optional<T> get(final Class<T> targetClass, final String sql, final StatementSetter statementSetter, final Object... parameters)
            throws DuplicatedResultException {
        return Optional.ofNullable(gett(targetClass, sql, statementSetter, parameters));
    }

    /**
     *
     * @param <T>
     * @param targetClass
     * @param sql
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> Optional<T> get(final Class<T> targetClass, final String sql, final JdbcSettings jdbcSettings, final Object... parameters)
            throws DuplicatedResultException {
        return Optional.ofNullable(gett(targetClass, sql, jdbcSettings, parameters));
    }

    /**
     *
     * @param <T>
     * @param targetClass
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> Optional<T> get(final Class<T> targetClass, final String sql, final StatementSetter statementSetter, final JdbcSettings jdbcSettings,
            final Object... parameters) throws DuplicatedResultException {
        return Optional.ofNullable(gett(targetClass, sql, statementSetter, jdbcSettings, parameters));
    }

    /**
     *
     * @param <T>
     * @param targetClass
     * @param conn
     * @param sql
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> Optional<T> get(final Class<T> targetClass, final Connection conn, final String sql, final Object... parameters)
            throws DuplicatedResultException {
        return Optional.ofNullable(gett(targetClass, conn, sql, parameters));
    }

    /**
     *
     * @param <T>
     * @param targetClass
     * @param conn
     * @param sql
     * @param statementSetter
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> Optional<T> get(final Class<T> targetClass, final Connection conn, final String sql, final StatementSetter statementSetter,
            final Object... parameters) throws DuplicatedResultException {
        return Optional.ofNullable(gett(targetClass, conn, sql, statementSetter, parameters));
    }

    /**
     *
     * @param <T>
     * @param targetClass
     * @param conn
     * @param sql
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> Optional<T> get(final Class<T> targetClass, final Connection conn, final String sql, final JdbcSettings jdbcSettings,
            final Object... parameters) throws DuplicatedResultException {
        return Optional.ofNullable(gett(targetClass, conn, sql, jdbcSettings, parameters));
    }

    /**
     *
     * @param <T>
     * @param targetClass
     * @param conn
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> Optional<T> get(final Class<T> targetClass, final Connection conn, final String sql, final StatementSetter statementSetter,
            JdbcSettings jdbcSettings, final Object... parameters) throws DuplicatedResultException {
        return Optional.ofNullable(gett(targetClass, conn, sql, statementSetter, jdbcSettings, parameters));
    }

    /**
     *
     * @param <T>
     * @param sql
     * @param rowMapper
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> Optional<T> get(final String sql, final JdbcUtil.RowMapper<T> rowMapper, final Object... parameters) throws DuplicatedResultException {
        return Optional.ofNullable(gett(sql, rowMapper, parameters));
    }

    /**
     *
     * @param <T>
     * @param sql
     * @param statementSetter
     * @param rowMapper
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    public final <T> Optional<T> get(final String sql, final StatementSetter statementSetter, final JdbcUtil.RowMapper<T> rowMapper, final Object... parameters)
            throws DuplicatedResultException {
        return Optional.ofNullable(gett(sql, statementSetter, rowMapper, parameters));
    }

    /**
     *
     * @param <T>
     * @param sql
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    public final <T> Optional<T> get(final String sql, final JdbcUtil.RowMapper<T> rowMapper, final JdbcSettings jdbcSettings, final Object... parameters)
            throws DuplicatedResultException {
        return Optional.ofNullable(gett(sql, rowMapper, jdbcSettings, parameters));
    }

    /**
     *
     *
     * @param <T>
     * @param sql
     * @param statementSetter
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> Optional<T> get(final String sql, final StatementSetter statementSetter, final JdbcUtil.RowMapper<T> rowMapper,
            final JdbcSettings jdbcSettings, final Object... parameters) throws DuplicatedResultException {
        return Optional.ofNullable(gett(sql, statementSetter, rowMapper, jdbcSettings, parameters));
    }

    /**
     *
     *
     * @param <T>
     * @param conn
     * @param sql
     * @param rowMapper
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> Optional<T> get(final Connection conn, final String sql, final JdbcUtil.RowMapper<T> rowMapper, final Object... parameters)
            throws DuplicatedResultException {
        return Optional.ofNullable(gett(conn, sql, rowMapper, parameters));
    }

    /**
     *
     * @param <T>
     * @param conn
     * @param sql
     * @param statementSetter
     * @param rowMapper
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> Optional<T> get(final Connection conn, final String sql, final StatementSetter statementSetter, final JdbcUtil.RowMapper<T> rowMapper,
            final Object... parameters) {
        return Optional.ofNullable(gett(conn, sql, statementSetter, rowMapper, parameters));
    }

    /**
     *
     * @param <T>
     * @param conn
     * @param sql
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> Optional<T> get(final Connection conn, final String sql, final JdbcUtil.RowMapper<T> rowMapper, JdbcSettings jdbcSettings,
            final Object... parameters) throws DuplicatedResultException {
        return Optional.ofNullable(gett(conn, sql, rowMapper, jdbcSettings, parameters));
    }

    /**
     *
     *
     * @param <T>
     * @param conn
     * @param sql
     * @param statementSetter
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> Optional<T> get(final Connection conn, final String sql, final StatementSetter statementSetter, final JdbcUtil.RowMapper<T> rowMapper,
            final JdbcSettings jdbcSettings, final Object... parameters) throws DuplicatedResultException {
        return Optional.ofNullable(gett(conn, sql, statementSetter, rowMapper, jdbcSettings, parameters));
    }

    /**
     * Gets the t.
     *
     * @param <T>
     * @param targetClass
     * @param sql
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> T gett(final Class<T> targetClass, final String sql, final Object... parameters) throws DuplicatedResultException {
        return gett(targetClass, sql, StatementSetter.DEFAULT, parameters);
    }

    /**
     * Gets the t.
     *
     * @param <T>
     * @param targetClass
     * @param sql
     * @param statementSetter
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> T gett(final Class<T> targetClass, final String sql, final StatementSetter statementSetter, final Object... parameters)
            throws DuplicatedResultException {
        return gett(targetClass, sql, statementSetter, null, parameters);
    }

    /**
     * Gets the t.
     *
     * @param <T>
     * @param targetClass
     * @param sql
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> T gett(final Class<T> targetClass, final String sql, final JdbcSettings jdbcSettings, final Object... parameters)
            throws DuplicatedResultException {
        return gett(targetClass, sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    /**
     * Gets the t.
     *
     * @param <T>
     * @param targetClass
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> T gett(final Class<T> targetClass, final String sql, final StatementSetter statementSetter, final JdbcSettings jdbcSettings,
            final Object... parameters) throws DuplicatedResultException {
        return gett(targetClass, null, sql, statementSetter, jdbcSettings, parameters);
    }

    /**
     * Gets the t.
     *
     * @param <T>
     * @param targetClass
     * @param conn
     * @param sql
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> T gett(final Class<T> targetClass, final Connection conn, final String sql, final Object... parameters) throws DuplicatedResultException {
        return gett(targetClass, conn, sql, StatementSetter.DEFAULT, parameters);
    }

    /**
     * Gets the t.
     *
     * @param <T>
     * @param targetClass
     * @param conn
     * @param sql
     * @param statementSetter
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> T gett(final Class<T> targetClass, final Connection conn, final String sql, final StatementSetter statementSetter,
            final Object... parameters) throws DuplicatedResultException {
        return gett(targetClass, conn, sql, statementSetter, null, parameters);
    }

    /**
     * Gets the t.
     *
     * @param <T>
     * @param targetClass
     * @param conn
     * @param sql
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> T gett(final Class<T> targetClass, final Connection conn, final String sql, final JdbcSettings jdbcSettings, final Object... parameters)
            throws DuplicatedResultException {
        return gett(targetClass, conn, sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    /**
     * Gets the t.
     *
     * @param <T>
     * @param targetClass
     * @param conn
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SuppressWarnings("unchecked")
    @SafeVarargs
    public final <T> T gett(final Class<T> targetClass, final Connection conn, final String sql, final StatementSetter statementSetter,
            JdbcSettings jdbcSettings, final Object... parameters) throws DuplicatedResultException {
        N.checkArgNotNull(targetClass, "targetClass");

        final JdbcUtil.RowMapper<T> rowMapper = toRowMapper(targetClass);

        return gett(conn, sql, statementSetter, rowMapper, jdbcSettings, parameters);
    }

    private <T> JdbcUtil.RowMapper<T> toRowMapper(final Class<T> targetClass) {
        N.checkArgNotNull(targetClass, "targetClass");

        final BiRowMapper<T> biRowMapper = BiRowMapper.to(targetClass);

        final JdbcUtil.RowMapper<T> rowMapper = new JdbcUtil.RowMapper<T>() {
            private List<String> columnLabels = null;

            @Override
            public T apply(ResultSet rs) throws SQLException {
                if (columnLabels == null) {
                    columnLabels = JdbcUtil.getColumnLabelList(rs);
                }

                return biRowMapper.apply(rs, columnLabels);
            }
        };

        return rowMapper;
    }

    /**
     * Gets the t.
     *
     * @param <T>
     * @param sql
     * @param rowMapper
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> T gett(final String sql, final JdbcUtil.RowMapper<T> rowMapper, final Object... parameters) throws DuplicatedResultException {
        return gett(sql, StatementSetter.DEFAULT, rowMapper, parameters);
    }

    /**
     * Gets the t.
     *
     * @param <T>
     * @param sql
     * @param statementSetter
     * @param rowMapper
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> T gett(final String sql, final StatementSetter statementSetter, final JdbcUtil.RowMapper<T> rowMapper, final Object... parameters)
            throws DuplicatedResultException {
        return gett(sql, statementSetter, rowMapper, null, parameters);
    }

    /**
     * Gets the t.
     *
     * @param <T>
     * @param sql
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> T gett(final String sql, final JdbcUtil.RowMapper<T> rowMapper, final JdbcSettings jdbcSettings, final Object... parameters)
            throws DuplicatedResultException {
        return gett(sql, StatementSetter.DEFAULT, rowMapper, jdbcSettings, parameters);
    }

    /**
     * Gets the t.
     *
     * @param <T>
     * @param sql
     * @param statementSetter
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> T gett(final String sql, final StatementSetter statementSetter, final JdbcUtil.RowMapper<T> rowMapper, final JdbcSettings jdbcSettings,
            final Object... parameters) throws DuplicatedResultException {
        return gett(null, sql, statementSetter, rowMapper, jdbcSettings, parameters);
    }

    /**
     * Gets the t.
     *
     * @param <T>
     * @param conn
     * @param sql
     * @param rowMapper
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> T gett(final Connection conn, final String sql, final JdbcUtil.RowMapper<T> rowMapper, final Object... parameters)
            throws DuplicatedResultException {
        return gett(conn, sql, StatementSetter.DEFAULT, rowMapper, parameters);
    }

    /**
     * Gets the t.
     *
     * @param <T>
     * @param conn
     * @param sql
     * @param statementSetter
     * @param rowMapper
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    public final <T> T gett(final Connection conn, final String sql, final StatementSetter statementSetter, final JdbcUtil.RowMapper<T> rowMapper,
            final Object... parameters) throws DuplicatedResultException {
        return gett(conn, sql, statementSetter, rowMapper, null, parameters);
    }

    /**
     * Gets the t.
     *
     * @param <T>
     * @param conn
     * @param sql
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    public final <T> T gett(final Connection conn, final String sql, final JdbcUtil.RowMapper<T> rowMapper, JdbcSettings jdbcSettings,
            final Object... parameters) throws DuplicatedResultException {
        return gett(conn, sql, StatementSetter.DEFAULT, rowMapper, jdbcSettings, parameters);
    }

    /**
     *
     * @param <T>
     * @param conn
     * @param sql
     * @param statementSetter
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SuppressWarnings("unchecked")
    @SafeVarargs
    public final <T> T gett(final Connection conn, final String sql, final StatementSetter statementSetter, final JdbcUtil.RowMapper<T> rowMapper,
            JdbcSettings jdbcSettings, final Object... parameters) throws DuplicatedResultException {
        N.checkArgNotNull(rowMapper, "rowMapper");

        final ResultExtractor<T> resultExtractor = new ResultExtractor<T>() {
            @Override
            public T apply(ResultSet rs) throws SQLException {
                T result = null;

                if (rs.next()) {
                    result = Objects.requireNonNull(rowMapper.apply(rs));

                    if (rs.next()) {
                        throw new DuplicatedResultException("More than one records found by sql: " + sql);
                    }
                }

                return result;
            }
        };

        return query(conn, sql, statementSetter, resultExtractor, jdbcSettings, parameters);
    }

    /**
     *
     * @param <T>
     * @param targetClass
     * @param sql
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final <T> Optional<T> findFirst(final Class<T> targetClass, final String sql, final Object... parameters) {
        return findFirst(targetClass, sql, StatementSetter.DEFAULT, parameters);
    }

    /**
     *
     * @param <T>
     * @param targetClass
     * @param sql
     * @param statementSetter
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final <T> Optional<T> findFirst(final Class<T> targetClass, final String sql, final StatementSetter statementSetter, final Object... parameters) {
        return findFirst(targetClass, sql, statementSetter, null, parameters);
    }

    /**
     *
     * @param <T>
     * @param targetClass
     * @param sql
     * @param jdbcSettings
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final <T> Optional<T> findFirst(final Class<T> targetClass, final String sql, final JdbcSettings jdbcSettings, final Object... parameters) {
        return findFirst(targetClass, sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    /**
     *
     * @param <T>
     * @param targetClass
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final <T> Optional<T> findFirst(final Class<T> targetClass, final String sql, final StatementSetter statementSetter, final JdbcSettings jdbcSettings,
            final Object... parameters) {
        return findFirst(targetClass, null, sql, statementSetter, jdbcSettings, parameters);
    }

    /**
     *
     * @param <T>
     * @param targetClass
     * @param conn
     * @param sql
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final <T> Optional<T> findFirst(final Class<T> targetClass, final Connection conn, final String sql, final Object... parameters) {
        return findFirst(targetClass, conn, sql, StatementSetter.DEFAULT, parameters);
    }

    /**
     *
     * @param <T>
     * @param targetClass
     * @param conn
     * @param sql
     * @param statementSetter
     * @param parameters
     * @return
     */
    public final <T> Optional<T> findFirst(final Class<T> targetClass, final Connection conn, final String sql, final StatementSetter statementSetter,
            final Object... parameters) {
        return findFirst(targetClass, conn, sql, statementSetter, null, parameters);
    }

    /**
     *
     * @param <T>
     * @param targetClass
     * @param conn
     * @param sql
     * @param jdbcSettings
     * @param parameters
     * @return
     */
    public final <T> Optional<T> findFirst(final Class<T> targetClass, final Connection conn, final String sql, final JdbcSettings jdbcSettings,
            final Object... parameters) {
        return findFirst(targetClass, conn, sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    /**
     * Just fetch the result in the 1st row. {@code null} is returned if no result is found. This method will try to
     * convert the column value to the type of mapping entity property if the mapping entity property is not assignable
     * from column value.
     *
     * Remember to add {@code limit} condition if big result will be returned by the query.
     *
     * @param <T>
     * @param targetClass
     * @param conn
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SuppressWarnings("unchecked")
    @SafeVarargs
    public final <T> Optional<T> findFirst(final Class<T> targetClass, final Connection conn, final String sql, final StatementSetter statementSetter,
            final JdbcSettings jdbcSettings, final Object... parameters) {
        N.checkArgNotNull(targetClass, "targetClass");

        final JdbcUtil.RowMapper<T> rowMapper = toRowMapper(targetClass);

        return findFirst(conn, sql, statementSetter, rowMapper, jdbcSettings, parameters);
    }

    /**
     *
     * @param <T>
     * @param sql
     * @param rowMapper
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final <T> Optional<T> findFirst(final String sql, final JdbcUtil.RowMapper<T> rowMapper, final Object... parameters) {
        return findFirst(sql, StatementSetter.DEFAULT, rowMapper, parameters);
    }

    /**
     *
     * @param <T>
     * @param sql
     * @param statementSetter
     * @param rowMapper
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final <T> Optional<T> findFirst(final String sql, final StatementSetter statementSetter, final JdbcUtil.RowMapper<T> rowMapper,
            final Object... parameters) {
        return findFirst(sql, statementSetter, rowMapper, null, parameters);
    }

    /**
     *
     * @param <T>
     * @param sql
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final <T> Optional<T> findFirst(final String sql, final JdbcUtil.RowMapper<T> rowMapper, final JdbcSettings jdbcSettings,
            final Object... parameters) {
        return findFirst(sql, StatementSetter.DEFAULT, rowMapper, jdbcSettings, parameters);
    }

    /**
     *
     * @param <T>
     * @param sql
     * @param statementSetter
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final <T> Optional<T> findFirst(final String sql, final StatementSetter statementSetter, final JdbcUtil.RowMapper<T> rowMapper,
            final JdbcSettings jdbcSettings, final Object... parameters) {
        return findFirst(null, sql, statementSetter, rowMapper, jdbcSettings, parameters);
    }

    /**
     *
     * @param <T>
     * @param conn
     * @param sql
     * @param rowMapper
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final <T> Optional<T> findFirst(final Connection conn, final String sql, final JdbcUtil.RowMapper<T> rowMapper, final Object... parameters) {
        return findFirst(conn, sql, StatementSetter.DEFAULT, rowMapper, parameters);
    }

    /**
     *
     * @param <T>
     * @param conn
     * @param sql
     * @param statementSetter
     * @param rowMapper
     * @param parameters
     * @return
     */
    public final <T> Optional<T> findFirst(final Connection conn, final String sql, final StatementSetter statementSetter,
            final JdbcUtil.RowMapper<T> rowMapper, final Object... parameters) {
        return findFirst(conn, sql, statementSetter, rowMapper, null, parameters);
    }

    /**
     *
     * @param <T>
     * @param conn
     * @param sql
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters
     * @return
     */
    public final <T> Optional<T> findFirst(final Connection conn, final String sql, final JdbcUtil.RowMapper<T> rowMapper, final JdbcSettings jdbcSettings,
            final Object... parameters) {
        return findFirst(conn, sql, StatementSetter.DEFAULT, rowMapper, jdbcSettings, parameters);
    }

    /**
     * Remember to add {@code limit} condition if big result will be returned by the query.
     *
     * @param <T>
     * @param conn
     * @param sql
     * @param statementSetter
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SuppressWarnings("unchecked")
    @SafeVarargs
    public final <T> Optional<T> findFirst(final Connection conn, final String sql, final StatementSetter statementSetter,
            final JdbcUtil.RowMapper<T> rowMapper, final JdbcSettings jdbcSettings, final Object... parameters) {
        N.checkArgNotNull(rowMapper, "rowMapper");

        final ResultExtractor<T> resultExtractor = new ResultExtractor<T>() {
            @Override
            public T apply(ResultSet rs) throws SQLException {
                return rs.next() ? Objects.requireNonNull(rowMapper.apply(rs)) : null;
            }
        };

        return Optional.ofNullable(query(conn, sql, statementSetter, resultExtractor, jdbcSettings, parameters));
    }

    /**
     *
     * @param <T>
     * @param targetClass
     * @param sql
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final <T> List<T> list(final Class<T> targetClass, final String sql, final Object... parameters) {
        return list(targetClass, sql, StatementSetter.DEFAULT, parameters);
    }

    /**
     *
     * @param <T>
     * @param targetClass
     * @param sql
     * @param statementSetter
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final <T> List<T> list(final Class<T> targetClass, final String sql, final StatementSetter statementSetter, final Object... parameters) {
        return list(targetClass, sql, statementSetter, null, parameters);
    }

    /**
     *
     * @param <T>
     * @param targetClass
     * @param sql
     * @param jdbcSettings
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final <T> List<T> list(final Class<T> targetClass, final String sql, final JdbcSettings jdbcSettings, final Object... parameters) {
        return list(targetClass, sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    /**
     *
     * @param <T>
     * @param targetClass
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final <T> List<T> list(final Class<T> targetClass, final String sql, final StatementSetter statementSetter, final JdbcSettings jdbcSettings,
            final Object... parameters) {
        return list(targetClass, null, sql, statementSetter, jdbcSettings, parameters);
    }

    /**
     *
     * @param <T>
     * @param targetClass
     * @param conn
     * @param sql
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final <T> List<T> list(final Class<T> targetClass, final Connection conn, final String sql, final Object... parameters) {
        return list(targetClass, conn, sql, StatementSetter.DEFAULT, parameters);
    }

    /**
     *
     * @param <T>
     * @param targetClass
     * @param conn
     * @param sql
     * @param statementSetter
     * @param parameters
     * @return
     */
    public final <T> List<T> list(final Class<T> targetClass, final Connection conn, final String sql, final StatementSetter statementSetter,
            final Object... parameters) {
        return list(targetClass, conn, sql, statementSetter, null, parameters);
    }

    /**
     *
     * @param <T>
     * @param targetClass
     * @param conn
     * @param sql
     * @param jdbcSettings
     * @param parameters
     * @return
     */
    public final <T> List<T> list(final Class<T> targetClass, final Connection conn, final String sql, final JdbcSettings jdbcSettings,
            final Object... parameters) {
        return list(targetClass, conn, sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    /**
     *
     * @param <T>
     * @param targetClass
     * @param conn
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SuppressWarnings("unchecked")
    @SafeVarargs
    public final <T> List<T> list(final Class<T> targetClass, final Connection conn, final String sql, final StatementSetter statementSetter,
            final JdbcSettings jdbcSettings, final Object... parameters) {
        return list(conn, sql, statementSetter, BiRowMapper.to(targetClass), jdbcSettings, parameters);
    }

    /**
     *
     * @param <T>
     * @param sql
     * @param rowMapper
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final <T> List<T> list(final String sql, final JdbcUtil.BiRowMapper<T> rowMapper, final Object... parameters) {
        return list(sql, StatementSetter.DEFAULT, rowMapper, parameters);
    }

    /**
     *
     * @param <T>
     * @param sql
     * @param statementSetter
     * @param rowMapper
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final <T> List<T> list(final String sql, final StatementSetter statementSetter, final JdbcUtil.BiRowMapper<T> rowMapper,
            final Object... parameters) {
        return list(sql, statementSetter, rowMapper, null, parameters);
    }

    /**
     *
     * @param <T>
     * @param sql
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final <T> List<T> list(final String sql, final JdbcUtil.BiRowMapper<T> rowMapper, final JdbcSettings jdbcSettings, final Object... parameters) {
        return list(sql, StatementSetter.DEFAULT, rowMapper, jdbcSettings, parameters);
    }

    /**
     *
     * @param <T>
     * @param sql
     * @param statementSetter
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final <T> List<T> list(final String sql, final StatementSetter statementSetter, final JdbcUtil.BiRowMapper<T> rowMapper,
            final JdbcSettings jdbcSettings, final Object... parameters) {
        return list(null, sql, statementSetter, rowMapper, jdbcSettings, parameters);
    }

    /**
     *
     * @param <T>
     * @param conn
     * @param sql
     * @param rowMapper
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final <T> List<T> list(final Connection conn, final String sql, final JdbcUtil.BiRowMapper<T> rowMapper, final Object... parameters) {
        return list(conn, sql, StatementSetter.DEFAULT, rowMapper, parameters);
    }

    /**
     *
     * @param <T>
     * @param conn
     * @param sql
     * @param statementSetter
     * @param rowMapper
     * @param parameters
     * @return
     */
    public final <T> List<T> list(final Connection conn, final String sql, final StatementSetter statementSetter, final JdbcUtil.BiRowMapper<T> rowMapper,
            final Object... parameters) {
        return list(conn, sql, statementSetter, rowMapper, null, parameters);
    }

    /**
     *
     * @param <T>
     * @param conn
     * @param sql
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters
     * @return
     */
    public final <T> List<T> list(final Connection conn, final String sql, final JdbcUtil.BiRowMapper<T> rowMapper, final JdbcSettings jdbcSettings,
            final Object... parameters) {
        return list(conn, sql, StatementSetter.DEFAULT, rowMapper, jdbcSettings, parameters);
    }

    /**
     *
     * @param <T>
     * @param conn
     * @param sql
     * @param statementSetter
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SuppressWarnings("unchecked")
    @SafeVarargs
    public final <T> List<T> list(final Connection conn, final String sql, final StatementSetter statementSetter, final JdbcUtil.BiRowMapper<T> rowMapper,
            final JdbcSettings jdbcSettings, final Object... parameters) {
        N.checkArgNotNull(rowMapper);

        final ResultExtractor<List<T>> resultExtractor = new ResultExtractor<List<T>>() {
            @Override
            public List<T> apply(ResultSet rs) throws SQLException {

                final List<T> result = new ArrayList<>();
                final List<String> columnLabels = JdbcUtil.getColumnLabelList(rs);

                while (rs.next()) {
                    result.add(rowMapper.apply(rs, columnLabels));
                }

                return result;
            }
        };

        return query(conn, sql, statementSetter, resultExtractor, jdbcSettings, parameters);
    }

    /**
     * Execute the query in one or more data sources specified by {@code jdbcSettings} and merge the results.
     * It's designed for partition.
     *
     * @param <T>
     * @param targetClass
     * @param sqls
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final <T> List<T> listAll(final Class<T> targetClass, final List<String> sqls, final JdbcSettings jdbcSettings, final Object... parameters) {
        return listAll(targetClass, sqls, null, jdbcSettings, parameters);
    }

    /**
     * Execute the query in one or more data sources specified by {@code jdbcSettings} and merge the results.
     * It's designed for partition.
     *
     * @param <T>
     * @param targetClass
     * @param sqls
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final <T> List<T> listAll(final Class<T> targetClass, final List<String> sqls, final StatementSetter statementSetter,
            final JdbcSettings jdbcSettings, final Object... parameters) {
        return listAll(sqls, statementSetter, BiRowMapper.to(targetClass), jdbcSettings, parameters);
    }

    /**
     * Execute one or more queries in one or more data sources specified by {@code jdbcSettings} and merge the results.
     * It's designed for partition.
     *
     * @param <T>
     * @param sqls
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final <T> List<T> listAll(final List<String> sqls, final JdbcUtil.BiRowMapper<T> rowMapper, final JdbcSettings jdbcSettings,
            final Object... parameters) {
        return listAll(sqls, null, rowMapper, jdbcSettings, parameters);
    }

    /**
     * Execute one or more queries in one or more data sources specified by {@code jdbcSettings} and merge the results.
     * It's designed for partition.
     *
     * @param <T>
     * @param sqls
     * @param statementSetter
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final <T> List<T> listAll(final List<String> sqls, final StatementSetter statementSetter, final JdbcUtil.BiRowMapper<T> rowMapper,
            final JdbcSettings jdbcSettings, final Object... parameters) {
        if (sqls.size() == 1) {
            return list(sqls.get(0), statementSetter, rowMapper, jdbcSettings, parameters);
        }

        List<List<T>> resultList = null;

        if (jdbcSettings != null && jdbcSettings.isQueryInParallel()) {
            resultList = Stream.of(sqls).parallel(sqls.size()).map(new Function<String, List<T>>() {
                @Override
                public List<T> apply(String sql) {
                    return list(sql, statementSetter, rowMapper, jdbcSettings, parameters);
                }
            }).toList();
        } else {
            resultList = new ArrayList<>(sqls.size());

            for (String sql : sqls) {
                resultList.add(list(sql, statementSetter, rowMapper, jdbcSettings, parameters));
            }
        }

        return N.concat(resultList);
    }

    /**
     * Query for boolean.
     *
     * @param sql
     * @param parameters
     * @return
     * @see SQLExecutor#queryForSingleResult(Class, Connection, String, Object...).
     */
    @SafeVarargs
    public final OptionalBoolean queryForBoolean(final String sql, final Object... parameters) {
        return query(sql, StatementSetter.DEFAULT, SINGLE_BOOLEAN_EXTRACTOR, null, parameters);
    }

    /**
     * Query for char.
     *
     * @param sql
     * @param parameters
     * @return
     * @see SQLExecutor#queryForSingleResult(Class, Connection, String, Object...).
     */
    @SafeVarargs
    public final OptionalChar queryForChar(final String sql, final Object... parameters) {
        return query(sql, StatementSetter.DEFAULT, SINGLE_CHAR_EXTRACTOR, null, parameters);
    }

    /**
     * Query for byte.
     *
     * @param sql
     * @param parameters
     * @return
     * @see SQLExecutor#queryForSingleResult(Class, Connection, String, Object...).
     */
    @SafeVarargs
    public final OptionalByte queryForByte(final String sql, final Object... parameters) {
        return query(sql, StatementSetter.DEFAULT, SINGLE_BYTE_EXTRACTOR, null, parameters);
    }

    /**
     * Query for short.
     *
     * @param sql
     * @param parameters
     * @return
     * @see SQLExecutor#queryForSingleResult(Class, Connection, String, Object...).
     */
    @SafeVarargs
    public final OptionalShort queryForShort(final String sql, final Object... parameters) {
        return query(sql, StatementSetter.DEFAULT, SINGLE_SHORT_EXTRACTOR, null, parameters);
    }

    /**
     * Query for int.
     *
     * @param sql
     * @param parameters
     * @return
     * @see SQLExecutor#queryForSingleResult(Class, Connection, String, Object...).
     */
    @SafeVarargs
    public final OptionalInt queryForInt(final String sql, final Object... parameters) {
        return query(sql, StatementSetter.DEFAULT, SINGLE_INT_EXTRACTOR, null, parameters);
    }

    /**
     * Query for long.
     *
     * @param sql
     * @param parameters
     * @return
     * @see SQLExecutor#queryForSingleResult(Class, Connection, String, Object...).
     */
    @SafeVarargs
    public final OptionalLong queryForLong(final String sql, final Object... parameters) {
        return query(sql, StatementSetter.DEFAULT, SINGLE_LONG_EXTRACTOR, null, parameters);
    }

    /**
     * Query for float.
     *
     * @param sql
     * @param parameters
     * @return
     * @see SQLExecutor#queryForSingleResult(Class, Connection, String, Object...).
     */
    @SafeVarargs
    public final OptionalFloat queryForFloat(final String sql, final Object... parameters) {
        return query(sql, StatementSetter.DEFAULT, SINGLE_FLOAT_EXTRACTOR, null, parameters);
    }

    /**
     * Query for double.
     *
     * @param sql
     * @param parameters
     * @return
     * @see SQLExecutor#queryForSingleResult(Class, Connection, String, Object...).
     */
    @SafeVarargs
    public final OptionalDouble queryForDouble(final String sql, final Object... parameters) {
        return query(sql, StatementSetter.DEFAULT, SINGLE_DOUBLE_EXTRACTOR, null, parameters);
    }

    /**
     * Query for big decimal.
     *
     * @param sql
     * @param parameters
     * @return
     * @see SQLExecutor#queryForSingleResult(Class, Connection, String, Object...).
     */
    @SafeVarargs
    public final Nullable<BigDecimal> queryForBigDecimal(final String sql, final Object... parameters) {
        return query(sql, StatementSetter.DEFAULT, SINGLE_BIG_DECIMAL_EXTRACTOR, null, parameters);
    }

    /**
     * Query for string.
     *
     * @param sql
     * @param parameters
     * @return
     * @see SQLExecutor#queryForSingleResult(Class, Connection, String, Object...).
     */
    @SafeVarargs
    public final Nullable<String> queryForString(final String sql, final Object... parameters) {
        return query(sql, StatementSetter.DEFAULT, SINGLE_STRING_EXTRACTOR, null, parameters);
    }

    /**
     * Query for date.
     *
     * @param sql
     * @param parameters
     * @return
     * @see SQLExecutor#queryForSingleResult(Class, String, Object...).
     */
    @SafeVarargs
    public final Nullable<Date> queryForDate(final String sql, final Object... parameters) {
        return query(sql, StatementSetter.DEFAULT, SINGLE_DATE_EXTRACTOR, null, parameters);
    }

    /**
     * Query for time.
     *
     * @param sql
     * @param parameters
     * @return
     * @see SQLExecutor#queryForSingleResult(Class, String, Object...).
     */
    @SafeVarargs
    public final Nullable<Time> queryForTime(final String sql, final Object... parameters) {
        return query(sql, StatementSetter.DEFAULT, SINGLE_TIME_EXTRACTOR, null, parameters);
    }

    /**
     * Query for timestamp.
     *
     * @param sql
     * @param parameters
     * @return
     * @see SQLExecutor#queryForSingleResult(Class, String, Object...).
     */
    @SafeVarargs
    public final Nullable<Timestamp> queryForTimestamp(final String sql, final Object... parameters) {
        return query(sql, StatementSetter.DEFAULT, SINGLE_TIMESTAMP_EXTRACTOR, null, parameters);
    }

    /**
     * Query for single result.
     *
     * @param <V> the value type
     * @param targetClass
     * @param sql
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final <V> Nullable<V> queryForSingleResult(final Class<V> targetClass, final String sql, final Object... parameters) {
        return queryForSingleResult(targetClass, sql, StatementSetter.DEFAULT, parameters);
    }

    /**
     * Query for single result.
     *
     * @param <V> the value type
     * @param targetClass
     * @param sql
     * @param statementSetter
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final <V> Nullable<V> queryForSingleResult(final Class<V> targetClass, final String sql, final StatementSetter statementSetter,
            final Object... parameters) {
        return queryForSingleResult(targetClass, sql, statementSetter, null, parameters);
    }

    /**
     * Query for single result.
     *
     * @param <V> the value type
     * @param targetClass
     * @param sql
     * @param jdbcSettings
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final <V> Nullable<V> queryForSingleResult(final Class<V> targetClass, final String sql, final JdbcSettings jdbcSettings,
            final Object... parameters) {
        return queryForSingleResult(targetClass, sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    /**
     * Query for single result.
     *
     * @param <V> the value type
     * @param targetClass
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final <V> Nullable<V> queryForSingleResult(final Class<V> targetClass, final String sql, final StatementSetter statementSetter,
            final JdbcSettings jdbcSettings, final Object... parameters) {
        return queryForSingleResult(targetClass, null, sql, statementSetter, jdbcSettings, parameters);
    }

    /**
     * Query for single result.
     *
     * @param <V> the value type
     * @param targetClass
     * @param conn
     * @param sql
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final <V> Nullable<V> queryForSingleResult(final Class<V> targetClass, final Connection conn, final String sql, final Object... parameters) {
        return queryForSingleResult(targetClass, conn, sql, StatementSetter.DEFAULT, parameters);
    }

    /**
     * Query for single result.
     *
     * @param <V> the value type
     * @param targetClass
     * @param conn
     * @param sql
     * @param statementSetter
     * @param parameters
     * @return
     */
    public final <V> Nullable<V> queryForSingleResult(final Class<V> targetClass, final Connection conn, final String sql,
            final StatementSetter statementSetter, final Object... parameters) {
        return queryForSingleResult(targetClass, conn, sql, statementSetter, null, parameters);
    }

    /**
     * Query for single result.
     *
     * @param <V> the value type
     * @param targetClass
     * @param conn
     * @param sql
     * @param jdbcSettings
     * @param parameters
     * @return
     */
    public final <V> Nullable<V> queryForSingleResult(final Class<V> targetClass, final Connection conn, final String sql, final JdbcSettings jdbcSettings,
            final Object... parameters) {
        return queryForSingleResult(targetClass, conn, sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    /**
     * Returns a {@code Nullable} describing the value in the first row/column if it exists, otherwise return an empty {@code Nullable}.
     * <br />
     *
     * Special note for type conversion for {@code boolean} or {@code Boolean} type: {@code true} is returned if the
     * {@code String} value of the target column is {@code "true"}, case insensitive. or it's an integer with value > 0.
     * Otherwise, {@code false} is returned.
     *
     * Remember to add {@code limit} condition if big result will be returned by the query.
     *
     * @param <V> the value type
     * @param targetClass set result type to avoid the NullPointerException if result is null and T is primitive type
     *            "int, long. short ... char, boolean..".
     * @param conn
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SuppressWarnings("unchecked")
    @SafeVarargs
    public final <V> Nullable<V> queryForSingleResult(final Class<V> targetClass, final Connection conn, final String sql,
            final StatementSetter statementSetter, final JdbcSettings jdbcSettings, final Object... parameters) {
        return query(conn, sql, statementSetter, createSingleResultExtractor(targetClass), jdbcSettings, parameters);
    }

    /** The single result extractor pool. */
    private final ObjectPool<Class<?>, ResultExtractor<Nullable<?>>> singleResultExtractorPool = new ObjectPool<>(64);

    /**
     * Creates the single result extractor.
     *
     * @param <V> the value type
     * @param targetClass
     * @return
     */
    private <V> ResultExtractor<Nullable<V>> createSingleResultExtractor(final Class<V> targetClass) {
        @SuppressWarnings("rawtypes")
        ResultExtractor result = singleResultExtractorPool.get(targetClass);

        if (result == null) {
            result = new ResultExtractor<Nullable<V>>() {
                @Override
                public Nullable<V> apply(final ResultSet rs) throws SQLException {
                    if (rs.next()) {
                        return Nullable.of(N.convert(JdbcUtil.getColumnValue(rs, 1), targetClass));
                    }

                    return Nullable.empty();
                }
            };

            singleResultExtractorPool.put(targetClass, result);
        }

        return result;
    }

    /**
     * Query for unique result.
     *
     * @param <V> the value type
     * @param targetClass
     * @param sql
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <V> Nullable<V> queryForUniqueResult(final Class<V> targetClass, final String sql, final Object... parameters)
            throws DuplicatedResultException {
        return queryForUniqueResult(targetClass, sql, StatementSetter.DEFAULT, parameters);
    }

    /**
     * Query for unique result.
     *
     * @param <V> the value type
     * @param targetClass
     * @param sql
     * @param statementSetter
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <V> Nullable<V> queryForUniqueResult(final Class<V> targetClass, final String sql, final StatementSetter statementSetter,
            final Object... parameters) throws DuplicatedResultException {
        return queryForUniqueResult(targetClass, sql, statementSetter, null, parameters);
    }

    /**
     * Query for unique result.
     *
     * @param <V> the value type
     * @param targetClass
     * @param sql
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <V> Nullable<V> queryForUniqueResult(final Class<V> targetClass, final String sql, final JdbcSettings jdbcSettings, final Object... parameters)
            throws DuplicatedResultException {
        return queryForUniqueResult(targetClass, sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    /**
     * Query for unique result.
     *
     * @param <V> the value type
     * @param targetClass
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <V> Nullable<V> queryForUniqueResult(final Class<V> targetClass, final String sql, final StatementSetter statementSetter,
            final JdbcSettings jdbcSettings, final Object... parameters) throws DuplicatedResultException {
        return queryForUniqueResult(targetClass, null, sql, statementSetter, jdbcSettings, parameters);
    }

    /**
     * Query for unique result.
     *
     * @param <V> the value type
     * @param targetClass
     * @param conn
     * @param sql
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <V> Nullable<V> queryForUniqueResult(final Class<V> targetClass, final Connection conn, final String sql, final Object... parameters)
            throws DuplicatedResultException {
        return queryForUniqueResult(targetClass, conn, sql, StatementSetter.DEFAULT, parameters);
    }

    /**
     * Query for unique result.
     *
     * @param <V> the value type
     * @param targetClass
     * @param conn
     * @param sql
     * @param statementSetter
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    public final <V> Nullable<V> queryForUniqueResult(final Class<V> targetClass, final Connection conn, final String sql,
            final StatementSetter statementSetter, final Object... parameters) throws DuplicatedResultException {
        return queryForUniqueResult(targetClass, conn, sql, statementSetter, null, parameters);
    }

    /**
     * Query for unique result.
     *
     * @param <V> the value type
     * @param targetClass
     * @param conn
     * @param sql
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    public final <V> Nullable<V> queryForUniqueResult(final Class<V> targetClass, final Connection conn, final String sql, final JdbcSettings jdbcSettings,
            final Object... parameters) throws DuplicatedResultException {
        return queryForUniqueResult(targetClass, conn, sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    /**
     * Returns a {@code Nullable} describing the value in the first row/column if it exists, otherwise return an empty {@code Nullable}.
     * And throws {@code DuplicatedResultException} if more than one record found.
     * <br />
     *
     * Special note for type conversion for {@code boolean} or {@code Boolean} type: {@code true} is returned if the
     * {@code String} value of the target column is {@code "true"}, case insensitive. or it's an integer with value > 0.
     * Otherwise, {@code false} is returned.
     *
     * Remember to add {@code limit} condition if big result will be returned by the query.
     *
     * @param <V> the value type
     * @param targetClass set result type to avoid the NullPointerException if result is null and T is primitive type
     *            "int, long. short ... char, boolean..".
     * @param conn
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if more than one record found.
     */
    @SuppressWarnings("unchecked")
    @SafeVarargs
    public final <V> Nullable<V> queryForUniqueResult(final Class<V> targetClass, final Connection conn, final String sql,
            final StatementSetter statementSetter, final JdbcSettings jdbcSettings, final Object... parameters) throws DuplicatedResultException {
        return query(conn, sql, statementSetter, createUniqueResultExtractor(targetClass), jdbcSettings, parameters);
    }

    /** The unique result extractor pool. */
    private final ObjectPool<Class<?>, ResultExtractor<Nullable<?>>> uniqueResultExtractorPool = new ObjectPool<>(64);

    /**
     * Creates the unique result extractor.
     *
     * @param <V> the value type
     * @param targetClass
     * @return
     */
    private <V> ResultExtractor<Nullable<V>> createUniqueResultExtractor(final Class<V> targetClass) {
        N.checkArgNotNull(targetClass, "targetClass");

        @SuppressWarnings("rawtypes")
        ResultExtractor result = uniqueResultExtractorPool.get(targetClass);

        if (result == null) {
            result = new ResultExtractor<Nullable<V>>() {
                @Override
                public Nullable<V> apply(final ResultSet rs) throws SQLException {
                    if (rs.next()) {
                        final Nullable<V> ret = Nullable.of(N.convert(JdbcUtil.getColumnValue(rs, 1), targetClass));

                        if (ret.isPresent() && rs.next()) {
                            throw new DuplicatedResultException(
                                    "At least two results found: " + Strings.concat(ret.get(), ", ", N.convert(JdbcUtil.getColumnValue(rs, 1), targetClass)));
                        }

                        return ret;
                    }

                    return Nullable.empty();
                }
            };

            uniqueResultExtractorPool.put(targetClass, result);
        }

        return result;
    }

    /**
     *
     * @param sql
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final DataSet query(final String sql, final Object... parameters) {
        return query(sql, StatementSetter.DEFAULT, parameters);
    }

    /**
     *
     * @param sql
     * @param statementSetter
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final DataSet query(final String sql, final StatementSetter statementSetter, final Object... parameters) {
        return query(sql, statementSetter, (JdbcSettings) null, parameters);
    }

    /**
     *
     * @param sql
     * @param jdbcSettings
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final DataSet query(final String sql, final JdbcSettings jdbcSettings, final Object... parameters) {
        return query(sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    /**
     *
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final DataSet query(final String sql, final StatementSetter statementSetter, final JdbcSettings jdbcSettings, final Object... parameters) {
        return query(sql, statementSetter, ResultExtractor.TO_DATA_SET, jdbcSettings, parameters);
    }

    /**
     *
     * @param <T>
     * @param sql
     * @param resultExtrator Don't save/return {@code ResultSet}. It will be closed after this call.
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final <T> T query(final String sql, final ResultExtractor<T> resultExtractor, final Object... parameters) {
        return query(sql, StatementSetter.DEFAULT, resultExtractor, parameters);
    }

    /**
     *
     * @param <T>
     * @param sql
     * @param statementSetter
     * @param resultExtrator Don't save/return {@code ResultSet}. It will be closed after this call.
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final <T> T query(final String sql, final StatementSetter statementSetter, final ResultExtractor<T> resultExtractor, final Object... parameters) {
        return query(sql, statementSetter, resultExtractor, null, parameters);
    }

    /**
     *
     * @param <T>
     * @param sql
     * @param resultExtrator Don't save/return {@code ResultSet}. It will be closed after this call.
     * @param jdbcSettings
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final <T> T query(final String sql, final ResultExtractor<T> resultExtractor, final JdbcSettings jdbcSettings, final Object... parameters) {
        return query(sql, StatementSetter.DEFAULT, resultExtractor, jdbcSettings, parameters);
    }

    /**
     *
     * Remember to close the <code>ResultSet</code>, <code>Statement</code> and <code>Connection</code> if the return type <code>T</code> is <code>ResultSet</code> or <code>RowIterator</code>.
     *
     * If <code>T</code> is <code>RowIterator</code>, call <code>rowIterator.close()</code> to close <code>ResultSet</code>, <code>Statement</code> and <code>Connection</code>.
     * <br></br>
     * If <code>T</code> is <code>ResultSet</code>, call below codes to close <code>ResultSet</code>, <code>Statement</code> and <code>Connection</code>.
     *
     * <pre>
     * <code>
     * Connection conn = null;
     * Statement stmt = null;
     *
     * try {
     *     stmt = rs.getStatement();
     *     conn = stmt.getConnection();
     * } catch (SQLException e) {
     *     // TODO.
     * } finally {
     *     JdbcUtil.closeQuietly(rs, stmt, conn);
     * }
     * </code>
     * </pre>
     *
     * @param <T>
     * @param sql
     * @param statementSetter
     * @param resultExtrator Don't save/return {@code ResultSet}. It will be closed after this call.
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final <T> T query(final String sql, final StatementSetter statementSetter, final ResultExtractor<T> resultExtractor, final JdbcSettings jdbcSettings,
            final Object... parameters) {
        return query(null, sql, statementSetter, resultExtractor, jdbcSettings, parameters);
    }

    /**
     *
     * @param conn
     * @param sql
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final DataSet query(final Connection conn, final String sql, final Object... parameters) {
        return query(conn, sql, StatementSetter.DEFAULT, parameters);
    }

    /**
     *
     * @param conn
     * @param sql
     * @param statementSetter
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final DataSet query(final Connection conn, final String sql, final StatementSetter statementSetter, final Object... parameters) {
        return query(conn, sql, statementSetter, (JdbcSettings) null, parameters);
    }

    /**
     *
     * @param conn
     * @param sql
     * @param jdbcSettings
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final DataSet query(final Connection conn, final String sql, final JdbcSettings jdbcSettings, final Object... parameters) {
        return query(conn, sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    /**
     *
     * @param conn
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final DataSet query(final Connection conn, final String sql, final StatementSetter statementSetter, final JdbcSettings jdbcSettings,
            final Object... parameters) {
        return query(conn, sql, statementSetter, ResultExtractor.TO_DATA_SET, jdbcSettings, parameters);
    }

    /**
     *
     * @param <T>
     * @param conn
     * @param sql
     * @param resultExtrator Don't save/return {@code ResultSet}. It will be closed after this call.
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final <T> T query(final Connection conn, final String sql, final ResultExtractor<T> resultExtractor, final Object... parameters) {
        return query(conn, sql, StatementSetter.DEFAULT, resultExtractor, parameters);
    }

    /**
     *
     * @param <T>
     * @param conn
     * @param sql
     * @param statementSetter
     * @param resultExtrator Don't save/return {@code ResultSet}. It will be closed after this call.
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final <T> T query(final Connection conn, final String sql, final StatementSetter statementSetter, final ResultExtractor<T> resultExtractor,
            final Object... parameters) {
        return query(conn, sql, statementSetter, resultExtractor, null, parameters);
    }

    /**
     *
     * @param <T>
     * @param conn
     * @param sql
     * @param resultExtrator Don't save/return {@code ResultSet}. It will be closed after this call.
     * @param jdbcSettings
     * @param parameters
     * @return
     */
    @SafeVarargs
    public final <T> T query(final Connection conn, final String sql, final ResultExtractor<T> resultExtractor, final JdbcSettings jdbcSettings,
            final Object... parameters) {
        return query(conn, sql, StatementSetter.DEFAULT, resultExtractor, jdbcSettings, parameters);
    }

    /**
     * Remember to close the <code>ResultSet</code>, <code>Statement</code> and <code>Connection</code> if the return type <code>T</code> is <code>ResultSet</code> or <code>RowIterator</code>.
     *
     * If <code>T</code> is <code>RowIterator</code>, call <code>rowIterator.close()</code> to close <code>ResultSet</code>, <code>Statement</code> and <code>Connection</code>.
     * <br></br>
     * If <code>T</code> is <code>ResultSet</code>, call below codes to close <code>ResultSet</code>, <code>Statement</code> and <code>Connection</code>.
     *
     * <pre>
     * <code>
     * Connection conn = null;
     * Statement stmt = null;
     *
     * try {
     *     stmt = rs.getStatement();
     *     conn = stmt.getConnection();
     * } catch (SQLException e) {
     *     // TODO.
     * } finally {
     *     JdbcUtil.closeQuietly(rs, stmt, conn);
     * }
     * </code>
     * </pre>
     *
     *
     * @param <T>
     * @param conn
     * @param sql
     * @param statementSetter
     * @param resultExtrator Don't save/return {@code ResultSet}. It will be closed after this call.
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final <T> T query(final Connection inputConn, final String sql, final StatementSetter statementSetter, final ResultExtractor<T> resultExtractor,
            final JdbcSettings jdbcSettings, final Object... parameters) {
        N.checkArgNotNull(resultExtractor, "resultExtractor");

        final ParsedSql parsedSql = getParsedSql(sql);
        final StatementSetter statementSetterToUse = checkStatementSetter(parsedSql, statementSetter);
        final JdbcSettings jdbcSettingsToUse = checkJdbcSettings(jdbcSettings, parsedSql, _sqlMapper.getAttrs(sql));
        final boolean isFromStreamQuery = resultExtractor == RESULT_EXTRACTOR_FOR_STREAM_ONLY;

        boolean noException = false;

        T result = null;

        DataSource ds = null;
        Connection localConn = null;
        PreparedStatement stmt = null;
        ResultSet rs = null;

        try {
            ds = getDataSource(parsedSql.getParameterizedSql(), parameters, jdbcSettingsToUse);

            localConn = getConnection(inputConn, ds, jdbcSettingsToUse, SQLOperation.SELECT);

            stmt = prepareStatement(ds, localConn, parsedSql, statementSetterToUse, jdbcSettingsToUse, false, false, parameters);

            if (jdbcSettingsToUse == null || jdbcSettingsToUse.getFetchDirection() == -1) {
                stmt.setFetchDirection(ResultSet.FETCH_FORWARD);
            }

            rs = JdbcUtil.executeQuery(stmt);

            result = resultExtractor.apply(rs);

            noException = true;
        } catch (SQLException e) {
            String msg = ExceptionUtil.getErrorMessage(e, true) + ". [SQL] " + parsedSql.sql();
            throw new UncheckedSQLException(msg, e);
        } finally {
            if (noException && result instanceof ResultSet) {
                if (isFromStreamQuery) {
                    // will be closed in stream.
                } else {
                    try {
                        close(rs, stmt);
                    } finally {
                        close(localConn, inputConn, ds);
                    }

                    throw new UnsupportedOperationException("The return type of 'ResultSetExtractor' can't be 'ResultSet'.");
                }
            } else {
                try {
                    close(rs, stmt);
                } finally {
                    close(localConn, inputConn, ds);
                }
            }
        }

        return result;
    }

    /**
     * Execute one or more queries in one or more data sources specified by {@code jdbcSettings} and merge the results.
     * It's designed for partition.
     *
     * @param sqls
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final DataSet queryAll(final List<String> sqls, final JdbcSettings jdbcSettings, final Object... parameters) {
        return queryAll(sqls, null, jdbcSettings, parameters);
    }

    /**
     * Execute one or more queries in one or more data sources specified by {@code jdbcSettings} and merge the results.
     * It's designed for partition.
     *
     * @param sqls
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final DataSet queryAll(final List<String> sqls, final StatementSetter statementSetter, final JdbcSettings jdbcSettings, final Object... parameters) {
        if (sqls.size() == 1) {
            return query(sqls.get(0), statementSetter, jdbcSettings, parameters);
        }

        if (jdbcSettings != null && jdbcSettings.isQueryInParallel()) {
            final List<DataSet> resultList = Stream.of(sqls).parallel(sqls.size()).map(new Function<String, DataSet>() {
                @Override
                public DataSet apply(String sql) {
                    return query(sql, statementSetter, jdbcSettings, parameters);
                }
            }).toList();

            return N.merge(resultList);
        } else {
            final List<DataSet> resultList = new ArrayList<>(sqls.size());

            for (String sql : sqls) {
                resultList.add(query(sql, statementSetter, jdbcSettings, parameters));
            }

            return N.merge(resultList);
        }
    }

    /**
     *
     * Lazy execution, lazy fetching. No connection fetching/creating, no statement preparing or execution, no result fetching until {@code @TerminalOp} or {@code @TerminalOpTriggered} stream operation is called.
     *
     * @param <T>
     * @param targetClass
     * @param sql
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    @LazyEvaluation
    public final <T> Stream<T> stream(final Class<T> targetClass, final String sql, final Object... parameters) {
        return stream(targetClass, sql, StatementSetter.DEFAULT, parameters);
    }

    /**
     * Lazy execution, lazy fetching. No connection fetching/creating, no statement preparing or execution, no result fetching until {@code @TerminalOp} or {@code @TerminalOpTriggered} stream operation is called.
     *
     * @param <T>
     * @param targetClass
     * @param sql
     * @param statementSetter
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    @LazyEvaluation
    public final <T> Stream<T> stream(final Class<T> targetClass, final String sql, final StatementSetter statementSetter, final Object... parameters) {
        return stream(targetClass, sql, statementSetter, null, parameters);
    }

    /**
     * Lazy execution, lazy fetching. No connection fetching/creating, no statement preparing or execution, no result fetching until {@code @TerminalOp} or {@code @TerminalOpTriggered} stream operation is called.
     *
     * @param <T>
     * @param targetClass
     * @param sql
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @LazyEvaluation
    public final <T> Stream<T> stream(final Class<T> targetClass, final String sql, final JdbcSettings jdbcSettings, final Object... parameters) {
        return stream(targetClass, sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    /**
     * Lazy execution, lazy fetching. No connection fetching/creating, no statement preparing or execution, no result fetching until {@code @TerminalOp} or {@code @TerminalOpTriggered} stream operation is called.
     *
     * @param <T>
     * @param targetClass
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    @LazyEvaluation
    public final <T> Stream<T> stream(final Class<T> targetClass, final String sql, final StatementSetter statementSetter, final JdbcSettings jdbcSettings,
            final Object... parameters) {
        return stream(sql, statementSetter, BiRowMapper.to(targetClass), jdbcSettings, parameters);
    }

    /**
     * Lazy execution, lazy fetching. No connection fetching/creating, no statement preparing or execution, no result fetching until {@code @TerminalOp} or {@code @TerminalOpTriggered} stream operation is called.
     *
     * @param <T>
     * @param sql
     * @param rowMapper
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    @LazyEvaluation
    public final <T> Stream<T> stream(final String sql, final JdbcUtil.BiRowMapper<T> rowMapper, final Object... parameters) {
        return stream(sql, StatementSetter.DEFAULT, rowMapper, parameters);
    }

    /**
     * Lazy execution, lazy fetching. No connection fetching/creating, no statement preparing or execution, no result fetching until {@code @TerminalOp} or {@code @TerminalOpTriggered} stream operation is called.
     *
     * @param <T>
     * @param sql
     * @param statementSetter
     * @param rowMapper
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    @LazyEvaluation
    public final <T> Stream<T> stream(final String sql, final StatementSetter statementSetter, final JdbcUtil.BiRowMapper<T> rowMapper,
            final Object... parameters) {
        return stream(sql, statementSetter, rowMapper, null, parameters);
    }

    /**
     * Lazy execution, lazy fetching. No connection fetching/creating, no statement preparing or execution, no result fetching until {@code @TerminalOp} or {@code @TerminalOpTriggered} stream operation is called.
     *
     * @param <T>
     * @param sql
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    @LazyEvaluation
    public final <T> Stream<T> stream(final String sql, final JdbcUtil.BiRowMapper<T> rowMapper, final JdbcSettings jdbcSettings, final Object... parameters) {
        return stream(sql, StatementSetter.DEFAULT, rowMapper, jdbcSettings, parameters);
    }

    /** The Constant RESULT_SET_EXTRACTOR. */
    private static final ResultExtractor<ResultSet> RESULT_EXTRACTOR_FOR_STREAM_ONLY = rs -> rs;

    /**
     * Lazy execution, lazy fetching. No connection fetching/creating, no statement preparing or execution, no result fetching until {@code @TerminalOp} or {@code @TerminalOpTriggered} stream operation is called.
     *
     * @param <T>
     * @param sql
     * @param statementSetter
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    @LazyEvaluation
    public final <T> Stream<T> stream(final String sql, final StatementSetter statementSetter, final JdbcUtil.BiRowMapper<T> rowMapper,
            final JdbcSettings jdbcSettings, final Object... parameters) {
        N.checkArgNotNull(rowMapper, "rowMapper");

        final ObjIteratorEx<T> lazyIter = ObjIteratorEx.of(new Supplier<ObjIteratorEx<T>>() {
            private ObjIteratorEx<T> internalIter = null;

            @Override
            public ObjIteratorEx<T> get() {
                if (internalIter == null) {
                    final Connection inputConn = null;

                    final JdbcSettings newJdbcSettings = jdbcSettings == null ? _jdbcSettings.copy() : jdbcSettings.copy();

                    final boolean noTransactionForStream = newJdbcSettings.noTransactionForStream();
                    final ParsedSql parsedSql = ParsedSql.parse(sql);
                    final DataSource ds = getDataSource(parsedSql.getParameterizedSql(), parameters, newJdbcSettings);
                    final Connection localConn = noTransactionForStream ? directGetConnectionFromPool(ds)
                            : getConnection(inputConn, ds, newJdbcSettings, SQLOperation.SELECT);
                    ResultSet resultSet = null;

                    try {
                        resultSet = query(localConn, sql, statementSetter, RESULT_EXTRACTOR_FOR_STREAM_ONLY, newJdbcSettings, parameters);
                        final ResultSet rs = resultSet;

                        internalIter = new ObjIteratorEx<T>() {
                            private boolean hasNext = false;
                            private List<String> columnLabels = null;

                            @Override
                            public boolean hasNext() {
                                if (hasNext == false) {
                                    try {
                                        if (rs.next()) {
                                            hasNext = true;
                                        }
                                    } catch (SQLException e) {
                                        throw new UncheckedSQLException(e);
                                    }
                                }

                                return hasNext;
                            }

                            @Override
                            public T next() {
                                if (hasNext() == false) {
                                    throw new NoSuchElementException();
                                }

                                try {
                                    if (columnLabels == null) {
                                        columnLabels = JdbcUtil.getColumnLabelList(rs);
                                    }

                                    final T result = rowMapper.apply(rs, columnLabels);
                                    hasNext = false;
                                    return result;
                                } catch (SQLException e) {
                                    throw new UncheckedSQLException(e);
                                }
                            }

                            @Override
                            public void advance(long n) {
                                N.checkArgNotNegative(n, "n");

                                final long m = hasNext ? n - 1 : n;
                                hasNext = false;

                                try {
                                    JdbcUtil.skip(rs, m);
                                } catch (SQLException e) {
                                    throw new UncheckedSQLException(e);
                                }
                            }

                            @Override
                            public long count() {
                                long result = hasNext ? 1 : 0;
                                hasNext = false;

                                try {
                                    while (rs.next()) {
                                        result++;
                                    }
                                } catch (SQLException e) {
                                    throw new UncheckedSQLException(e);
                                }

                                return result;
                            }

                            @Override
                            public void close() {
                                try {
                                    JdbcUtil.closeQuietly(rs, true, false);
                                } finally {
                                    if (noTransactionForStream) {
                                        SQLExecutor.this.close(localConn, ds);
                                    } else {
                                        SQLExecutor.this.close(localConn, inputConn, ds);
                                    }
                                }
                            }
                        };
                    } finally {
                        if (internalIter == null) {
                            try {
                                JdbcUtil.closeQuietly(resultSet, true, false);
                            } finally {
                                if (noTransactionForStream) {
                                    SQLExecutor.this.close(localConn, ds);
                                } else {
                                    SQLExecutor.this.close(localConn, inputConn, ds);
                                }
                            }
                        }
                    }
                }

                return internalIter;
            }
        });

        return Stream.of(lazyIter).onClose(new Runnable() {
            @Override
            public void run() {
                lazyIter.close();
            }
        });
    }

    private static Connection directGetConnectionFromPool(final DataSource ds) throws UncheckedSQLException {
        try {
            return ds.getConnection();
        } catch (SQLException e) {
            throw new UncheckedSQLException(e);
        }
    }

    /**
     * Lazy execution, lazy fetching. No connection fetching/creating, no statement preparing or execution, no result fetching until {@code @TerminalOp} or {@code @TerminalOpTriggered} stream operation is called.
     *
     * <br />
     *
     * Execute one or more queries in one or more data sources specified by {@code jdbcSettings} and merge the results.
     * It's designed for partition.
     *
     * @param <T>
     * @param targetClass
     * @param sqls
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    @LazyEvaluation
    public final <T> Stream<T> streamAll(final Class<T> targetClass, final List<String> sqls, final JdbcSettings jdbcSettings, final Object... parameters) {
        return streamAll(targetClass, sqls, null, jdbcSettings, parameters);
    }

    /**
     * Lazy execution, lazy fetching. No connection fetching/creating, no statement preparing or execution, no result fetching until {@code @TerminalOp} or {@code @TerminalOpTriggered} stream operation is called.
     *
     * <br />
     *
     * Execute one or more queries in one or more data sources specified by {@code jdbcSettings} and merge the results.
     * It's designed for partition.
     *
     * @param <T>
     * @param targetClass
     * @param sqls
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    @LazyEvaluation
    public final <T> Stream<T> streamAll(final Class<T> targetClass, final List<String> sqls, final StatementSetter statementSetter,
            final JdbcSettings jdbcSettings, final Object... parameters) {
        return streamAll(sqls, statementSetter, BiRowMapper.to(targetClass), jdbcSettings, parameters);
    }

    /**
     * Lazy execution, lazy fetching. No connection fetching/creating, no statement preparing or execution, no result fetching until {@code @TerminalOp} or {@code @TerminalOpTriggered} stream operation is called.
     *
     * <br />
     *
     * Execute one or more queries in one or more data sources specified by {@code jdbcSettings} and merge the results.
     * It's designed for partition.
     *
     * @param <T>
     * @param sqls
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    @LazyEvaluation
    public final <T> Stream<T> streamAll(final List<String> sqls, final JdbcUtil.BiRowMapper<T> rowMapper, final JdbcSettings jdbcSettings,
            final Object... parameters) {
        return streamAll(sqls, null, rowMapper, jdbcSettings, parameters);
    }

    /**
     * Lazy execution, lazy fetching. No connection fetching/creating, no statement preparing or execution, no result fetching until {@code @TerminalOp} or {@code @TerminalOpTriggered} stream operation is called.
     *
     * <br />
     *
     * Execute one or more queries in one or more data sources specified by {@code jdbcSettings} and merge the results.
     * It's designed for partition.
     *
     * @param <T>
     * @param sqls
     * @param statementSetter
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    @LazyEvaluation
    public final <T> Stream<T> streamAll(final List<String> sqls, final StatementSetter statementSetter, final JdbcUtil.BiRowMapper<T> rowMapper,
            final JdbcSettings jdbcSettings, final Object... parameters) {
        N.checkArgNotNullOrEmpty(sqls, "sqls");
        N.checkArgNotNull(rowMapper, "rowMapper");

        if (sqls.size() == 1) {
            return stream(sqls.get(0), statementSetter, rowMapper, jdbcSettings, parameters);
        }

        final boolean isQueryInParallel = jdbcSettings != null && jdbcSettings.isQueryInParallel();

        return Stream.of(sqls).__(new Function<Stream<String>, Stream<String>>() {
            @Override
            public Stream<String> apply(Stream<String> s) {
                return isQueryInParallel ? s.parallel(sqls.size()) : s;
            }
        }).flatMap(new Function<String, Stream<T>>() {
            @Override
            public Stream<T> apply(String sql) {
                return stream(sql, statementSetter, rowMapper, jdbcSettings, parameters);
            }
        });
    }

    /**
     *
     * @param sql
     * @param parameters
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    public final boolean execute(final String sql, final Object... parameters) throws UncheckedSQLException {
        return execute(null, sql, parameters);
    }

    /**
     * Execute the sql with the specified parameters.
     *
     * @param conn
     * @param sql
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @throws UncheckedSQLException the unchecked SQL exception
     */
    @SafeVarargs
    public final boolean execute(final Connection conn, final String sql, final Object... parameters) throws UncheckedSQLException {
        final ParsedSql parsedSql = getParsedSql(sql);
        final StatementSetter statementSetter = checkStatementSetter(parsedSql, null);
        final JdbcSettings jdbcSettings = checkJdbcSettings(null, parsedSql, _sqlMapper.getAttrs(sql));

        final SQLOperation sqlOperation = JdbcUtil.getSQLOperation(parsedSql.getParameterizedSql());
        DataSource ds = null;
        Connection localConn = null;
        PreparedStatement stmt = null;

        try {
            ds = getDataSource(parsedSql.getParameterizedSql(), parameters, jdbcSettings);

            localConn = getConnection(conn, ds, jdbcSettings, sqlOperation);

            stmt = prepareStatement(ds, localConn, parsedSql, statementSetter, jdbcSettings, false, false, parameters);

            return JdbcUtil.execute(stmt);
        } catch (SQLException e) {
            String msg = ExceptionUtil.getErrorMessage(e, true) + ". [SQL] " + parsedSql.sql();
            throw new UncheckedSQLException(msg, e);
        } finally {
            close(stmt);
            close(localConn, conn, ds);
        }
    }

    /**
     * Refer to {@code beginTransaction(IsolationLevel, boolean, JdbcSettings)}.
     *
     * @return
     * @see #beginTransaction(IsolationLevel, boolean, JdbcSettings)
     */
    public SQLTransaction beginTransaction() {
        return beginTransaction(IsolationLevel.DEFAULT);
    }

    /**
     *
     * Refer to {@code beginTransaction(IsolationLevel, boolean, JdbcSettings)}.
     *
     * @param isolationLevel
     * @return
     * @see #beginTransaction(IsolationLevel, boolean, JdbcSettings)
     */
    public SQLTransaction beginTransaction(final IsolationLevel isolationLevel) {
        return beginTransaction(isolationLevel, false);
    }

    /**
     *
     * Refer to {@code beginTransaction(IsolationLevel, boolean, JdbcSettings)}.
     *
     * @param forUpdateOnly
     * @return
     * @see #beginTransaction(IsolationLevel, boolean, JdbcSettings)
     */
    public SQLTransaction beginTransaction(final boolean forUpdateOnly) {
        return beginTransaction(IsolationLevel.DEFAULT, forUpdateOnly);
    }

    /**
     *
     * Refer to {@code beginTransaction(IsolationLevel, boolean, JdbcSettings)}.
     *
     * @param isolationLevel
     * @param forUpdateOnly
     * @return
     * @see #beginTransaction(IsolationLevel, boolean, JdbcSettings)
     */
    public SQLTransaction beginTransaction(IsolationLevel isolationLevel, boolean forUpdateOnly) {
        return beginTransaction(isolationLevel, forUpdateOnly, null);
    }

    /**
     * If this method is called where a transaction is started by {@code JdbcUtil.beginTransaction} or in {@code Spring} with the same {@code DataSource} in the same thread,
     * the {@code Connection} started the Transaction will be used here.
     * That's to say the transaction started by {@code JdbcUtil.beginTransaction} or in {@code Spring} will have the final control on commit/roll back over the {@code Connection}.
     * <br />
     * Otherwise a {@code Connection} directly from the specified {@code DataSource}(Connection pool) will be borrowed and used.
     * <br />
     * Transactions started by {@code SQLExecutor.beginTransaction} won't be shared by {@code JdbcUtil.beginTransaction} or Spring.
     *
     * <br />
     * <br />
     *
     * The connection opened in the transaction will be automatically closed after the transaction is committed or rolled back.
     * DON'T close it again by calling the close method.
     *
     * Transaction can be started:
     *
     * <pre>
     * <code>
     *   final SQLTransaction tran = sqlExecutor.beginTransaction(IsolationLevel.READ_COMMITTED);
     *   try {
     *       // sqlExecutor.insert(...);
     *       // sqlExecutor.update(...);
     *       // sqlExecutor.query(...);
     *
     *       tran.commit();
     *   } finally {
     *       // The connection will be automatically closed after the transaction is committed or rolled back.
     *       tran.rollbackIfNotCommitted();
     *   }
     * </code>
     * </pre>
     *
     * @param isolationLevel
     * @param forUpdateOnly
     * @param jdbcSettings
     * @return
     */
    public SQLTransaction beginTransaction(final IsolationLevel isolationLevel, final boolean forUpdateOnly,
            @SuppressWarnings("unused") final JdbcSettings jdbcSettings) {
        N.checkArgNotNull(isolationLevel, "isolationLevel");

        //    final DataSource ds = jdbcSettings != null && jdbcSettings.getQueryWithDataSource() != null
        //            ? getDataSource(N.EMPTY_STRING, N.EMPTY_OBJECT_ARRAY, jdbcSettings)
        //            : _ds;

        //    final DataSource ds = _ds;
        //
        //    SQLTransaction tran = SQLTransaction.getTransaction(ds, CreatedBy.JDBC_UTIL);
        //
        //    if (tran == null) {
        //        Connection conn = null;
        //        boolean noException = false;
        //
        //        try {
        //            conn = getConnection(ds);
        //            tran = new SQLTransaction(ds, conn, isolationLevel == IsolationLevel.DEFAULT ? _defaultIsolationLevel : isolationLevel, CreatedBy.JDBC_UTIL,
        //                    true);
        //            tran.incrementAndGetRef(isolationLevel, forUpdateOnly);
        //
        //            noException = true;
        //        } catch (SQLException e) {
        //            throw new UncheckedSQLException(e);
        //        } finally {
        //            if (noException == false) {
        //                close(conn, ds);
        //            }
        //        }
        //
        //        logger.info("Create a new SQLTransaction(id={})", tran.id());
        //        SQLTransaction.putTransaction(tran);
        //    } else {
        //        logger.info("Reusing the existing SQLTransaction(id={})", tran.id());
        //        tran.incrementAndGetRef(isolationLevel, forUpdateOnly);
        //    }
        //
        //    return tran;

        return JdbcUtil.beginTransaction(_ds, isolationLevel, forUpdateOnly);
    }

    /**
     * Does table exist.
     *
     * @param tableName
     * @return true, if successful
     */
    public boolean doesTableExist(final String tableName) {
        N.checkArgNotNullOrEmpty(tableName, "tableName");

        Connection conn = getConnection();

        try {
            return JdbcUtil.doesTableExist(conn, tableName);
        } finally {
            closeConnection(conn);
        }
    }

    /**
     * Returns {@code true} if succeed to create table, otherwise {@code false} is returned.
     *
     * @param tableName
     * @param schema
     * @return true, if successful
     */
    public boolean createTableIfNotExists(final String tableName, final String schema) {
        N.checkArgNotNullOrEmpty(tableName, "tableName");

        Connection conn = getConnection();

        try {
            return JdbcUtil.createTableIfNotExists(conn, tableName, schema);
        } finally {
            closeConnection(conn);
        }
    }

    /**
     * Returns {@code true} if succeed to drop table, otherwise {@code false} is returned.
     *
     * @param tableName
     * @return true, if successful
     */
    public boolean dropTableIfExists(final String tableName) {
        N.checkArgNotNullOrEmpty(tableName, "tableName");

        Connection conn = getConnection();

        try {
            return JdbcUtil.dropTableIfExists(conn, tableName);
        } finally {
            closeConnection(conn);
        }
    }

    /**
     * Gets the column name list.
     *
     * @param tableName
     * @return
     */
    public ImmutableList<String> getColumnNameList(final String tableName) {
        N.checkArgNotNullOrEmpty(tableName, "tableName");

        ImmutableList<String> columnNameList = _tableColumnNamePool.get(tableName);

        if (columnNameList == null) {
            Connection conn = getConnection();

            try {
                columnNameList = ImmutableList.of(JdbcUtil.getColumnNameList(conn, tableName));
                _tableColumnNamePool.put(tableName, columnNameList);
            } catch (SQLException e) {
                throw new UncheckedSQLException(e);
            } finally {
                closeConnection(conn);
            }
        }

        return columnNameList;
    }

    /**
     * Gets the connection.
     *
     * @return
     */
    public Connection getConnection() {
        return getConnection(_ds);
    }

    /**
     *
     * @param conn
     */
    public void closeConnection(final Connection conn) {
        close(conn, _ds);
    }

    /**
     * Gets the data source.
     *
     * @param sql
     * @param parameters
     * @param jdbcSettings
     * @return
     */
    @SuppressWarnings("unused")
    protected DataSource getDataSource(final String sql, final Object[] parameters, final JdbcSettings jdbcSettings) {
        return _ds;
    }

    /**
     * Gets the data source.
     *
     * @param sql
     * @param parametersList
     * @param jdbcSettings
     * @return
     */
    @SuppressWarnings("unused")
    protected DataSource getDataSource(final String sql, final List<?> parametersList, final JdbcSettings jdbcSettings) {
        return _ds;
    }

    /**
     * Gets the connection.
     *
     * @param inputConn
     * @param ds
     * @param jdbcSettings
     * @param op
     * @return
     */
    @SuppressWarnings("unused")
    protected Connection getConnection(final Connection inputConn, final DataSource ds, final JdbcSettings jdbcSettings, final SQLOperation op) {
        if (inputConn != null) {
            return inputConn;
        }

        final SQLTransaction tran = SQLTransaction.getTransaction(ds, CreatedBy.JDBC_UTIL);

        if (tran == null || (tran.isForUpdateOnly() && op == SQLOperation.SELECT)) {
            return getConnection(ds);
        }

        return tran.connection();
    }

    /**
     * Gets the connection.
     *
     * @param ds
     * @return
     */
    @SuppressWarnings("static-method")
    protected Connection getConnection(final DataSource ds) {
        return JdbcUtil.getConnection(ds);
    }

    /**
     *
     * @param ds
     * @param localConn
     * @param parsedSql
     * @param statementSetter
     * @param jdbcSettings
     * @param autoGeneratedKeys
     * @param isBatch
     * @param parameters
     * @return
     * @throws SQLException the SQL exception
     */
    @SuppressWarnings({ "unused", "resource", "deprecation" })
    protected PreparedStatement prepareStatement(final DataSource ds, final Connection localConn, final ParsedSql parsedSql,
            final StatementSetter statementSetter, final JdbcSettings jdbcSettings, final boolean autoGeneratedKeys, final boolean isBatch,
            final Object... parameters) throws SQLException {
        final String sql = parsedSql.getParameterizedSql();

        PreparedStatement stmt = null;

        if (jdbcSettings == null || jdbcSettings.isLogSQL() == false) {
            stmt = prepareStatement(localConn, sql, autoGeneratedKeys, jdbcSettings);
        } else {
            final SqlLogConfig sqlLogConfig = JdbcUtil.isSQLLogEnabled_TL.get();
            final boolean prevSqlLogEnabled = sqlLogConfig.isEnabled;
            final int prevMaxSqlLogLength = sqlLogConfig.maxSqlLogLength;

            JdbcUtil.enableSqlLog();

            try {
                stmt = prepareStatement(localConn, sql, autoGeneratedKeys, jdbcSettings);
            } finally {
                JdbcUtil.enableSqlLog(prevSqlLogEnabled, prevMaxSqlLogLength);
            }
        }

        setParameters(parsedSql, stmt, statementSetter, isBatch, parameters);

        return stmt;
    }

    /**
     *
     * @param conn
     * @param sql
     * @param autoGeneratedKeys
     * @param jdbcSettings
     * @return
     * @throws SQLException the SQL exception
     */
    @SuppressWarnings({ "static-method", "resource" })
    protected PreparedStatement prepareStatement(final Connection conn, String sql, final boolean autoGeneratedKeys, final JdbcSettings jdbcSettings)
            throws SQLException {
        PreparedStatement stmt = null;

        if (jdbcSettings == null) {
            stmt = JdbcUtil.prepareStatement(conn, sql, autoGeneratedKeys);
        } else {
            if (N.notNullOrEmpty(jdbcSettings.getReturnedColumnIndexes())) {
                //    if (jdbcSettings.getReturnedColumnIndexes().length != 1) {
                //        throw new IllegalArgumentException("only 1 generated key is supported At present");
                //    }

                stmt = JdbcUtil.prepareStatement(conn, sql, jdbcSettings.getReturnedColumnIndexes());
            } else if (N.notNullOrEmpty(jdbcSettings.getReturnedColumnNames())) {
                //    if (jdbcSettings.getReturnedColumnNames().length != 1) {
                //        throw new IllegalArgumentException("only 1 generated key is supported At present");
                //    }

                stmt = JdbcUtil.prepareStatement(conn, sql, jdbcSettings.getReturnedColumnNames());
            } else if (jdbcSettings.isAutoGeneratedKeys() || autoGeneratedKeys) {
                stmt = JdbcUtil.prepareStatement(conn, sql, jdbcSettings.isAutoGeneratedKeys() || autoGeneratedKeys);
            } else if ((jdbcSettings.getResultSetType() != -1) || (jdbcSettings.getResultSetConcurrency() != -1)
                    || (jdbcSettings.getResultSetHoldability() != -1)) {
                int resultSetType = (jdbcSettings.getResultSetType() == -1) ? JdbcSettings.DEFAULT_RESULT_SET_TYPE : jdbcSettings.getResultSetType();

                int resultSetConcurrency = (jdbcSettings.getResultSetConcurrency() == -1) ? JdbcSettings.DEFAULT_RESULT_SET_CONCURRENCY
                        : jdbcSettings.getResultSetConcurrency();

                if (jdbcSettings.getResultSetHoldability() != -1) {
                    JdbcUtil.prepareStatement(conn, sql, resultSetType, resultSetConcurrency, jdbcSettings.getResultSetHoldability());
                } else {
                    JdbcUtil.prepareStatement(conn, sql, resultSetType, resultSetConcurrency);
                }
            } else {
                stmt = JdbcUtil.prepareStatement(conn, sql);
            }

            if (jdbcSettings.getFetchSize() != -1) {
                stmt.setFetchSize(jdbcSettings.getFetchSize());
            }

            if (jdbcSettings.getMaxRows() != -1) {
                stmt.setMaxRows(jdbcSettings.getMaxRows());
            }

            if (jdbcSettings.getMaxFieldSize() != -1) {
                stmt.setMaxFieldSize(jdbcSettings.getMaxFieldSize());
            }

            if (jdbcSettings.getFetchDirection() != -1) {
                stmt.setFetchDirection(jdbcSettings.getFetchDirection());
            }

            if (jdbcSettings.getQueryTimeout() != -1) {
                stmt.setQueryTimeout(jdbcSettings.getQueryTimeout());
            }
        }

        return stmt;
    }

    //    protected CallableStatement prepareCallableStatement(final DataSource ds, final Connection localConn, final ParsedSql parsedSql,
    //            final StatementSetter statementSetter, final JdbcSettings jdbcSettings, final boolean autoGeneratedKeys, final boolean isBatch,
    //            final Object... parameters) throws SQLException {
    //        String sql = parsedSql.getPureSQL();
    //
    //        if (isBatch) {
    //            sql = ds.getSliceSelector().select(null, sql, (List<?>) parameters[0], null);
    //        } else {
    //            sql = ds.getSliceSelector().select(null, sql, parameters, null);
    //        }
    //
    //        logSQL(sql, jdbcSettings, parameters);
    //
    //        final CallableStatement stmt = prepareCallableStatement(localConn, sql, jdbcSettings);
    //
    //        setParameters(parsedSql, stmt, statementSetter, isBatch, parameters);
    //
    //        return stmt;
    //    }
    //
    //    protected CallableStatement prepareCallableStatement(final Connection conn, String sql, final JdbcSettings jdbcSettings) throws SQLException {
    //        CallableStatement stmt = null;
    //
    //        if (jdbcSettings == null) {
    //            stmt = conn.prepareCall(sql);
    //        } else {
    //            if ((jdbcSettings.getResultSetType() != -1) || (jdbcSettings.getResultSetConcurrency() != -1) || (jdbcSettings.getResultSetHoldability() != -1)) {
    //                int resultSetType = (jdbcSettings.getResultSetType() == -1) ? JdbcSettings.DEFAULT_RESULT_SET_TYPE : jdbcSettings.getResultSetType();
    //
    //                int resultSetConcurrency = (jdbcSettings.getResultSetConcurrency() == -1) ? JdbcSettings.DEFAULT_RESULT_SET_CONCURRENCY
    //                        : jdbcSettings.getResultSetConcurrency();
    //
    //                if (jdbcSettings.getResultSetHoldability() != -1) {
    //                    stmt = conn.prepareCall(sql, resultSetType, resultSetConcurrency, jdbcSettings.getResultSetHoldability());
    //                } else {
    //                    stmt = conn.prepareCall(sql, resultSetType, resultSetConcurrency);
    //                }
    //            } else {
    //                stmt = conn.prepareCall(sql);
    //            }
    //
    //            if (jdbcSettings.getFetchSize() != -1) {
    //                stmt.setFetchSize(jdbcSettings.getFetchSize());
    //            }
    //
    //            if (jdbcSettings.getMaxRows() != -1) {
    //                stmt.setMaxRows(jdbcSettings.getMaxRows());
    //            }
    //
    //            if (jdbcSettings.getMaxFieldSize() != -1) {
    //                stmt.setMaxFieldSize(jdbcSettings.getMaxFieldSize());
    //            }
    //
    //            if (jdbcSettings.getFetchDirection() != -1) {
    //                stmt.setFetchDirection(jdbcSettings.getFetchDirection());
    //            }
    //
    //            if (jdbcSettings.getQueryTimeout() != -1) {
    //                stmt.setQueryTimeout(jdbcSettings.getQueryTimeout());
    //            }
    //        }
    //        return stmt;
    //    }

    /**
     * Sets the parameters.
     *
     * @param parsedSql
     * @param stmt
     * @param statementSetter
     * @param isBatch
     * @param parameters
     * @throws SQLException the SQL exception
     */
    @SuppressWarnings("static-method")
    protected void setParameters(final ParsedSql parsedSql, final PreparedStatement stmt, final StatementSetter statementSetter, final boolean isBatch,
            final Object... parameters) throws SQLException {
        if (isBatch || (N.isNullOrEmpty(parameters) && statementSetter == StatementSetter.DEFAULT)) {
            // ignore
        } else {
            statementSetter.accept(parsedSql, stmt, parameters);
        }
    }

    /**
     *
     * @param rs
     */
    @SuppressWarnings("static-method")
    protected void close(final ResultSet rs) {
        JdbcUtil.closeQuietly(rs);
    }

    /**
     *
     * @param stmt
     */
    @SuppressWarnings("static-method")
    protected void close(final PreparedStatement stmt) {
        JdbcUtil.closeQuietly(stmt);
    }

    /**
     *
     * @param rs
     * @param stmt
     */
    @SuppressWarnings("static-method")
    protected void close(final ResultSet rs, final PreparedStatement stmt) {
        JdbcUtil.closeQuietly(rs, stmt);
    }

    /**
     *
     * @param localConn
     * @param inputConn
     * @param ds
     */
    protected void close(final Connection localConn, final Connection inputConn, final DataSource ds) {
        if (inputConn == null) {
            final SQLTransaction tran = SQLTransaction.getTransaction(ds, CreatedBy.JDBC_UTIL);

            if (tran != null && tran.connection() == localConn) {
                // ignore.
            } else {
                close(localConn, ds);
            }
        }
    }

    /**
     *
     * @param conn
     * @param ds
     */
    @SuppressWarnings("static-method")
    protected void close(final Connection conn, final DataSource ds) {
        JdbcUtil.releaseConnection(conn, ds);
    }

    //    /**
    //     * Close the underline data source.
    //     */
    //    public void close() {
    //        // Nothing to close.
    //    }

    /**
     * Gets the batch size.
     *
     * @param jdbcSettings
     * @return
     */
    @SuppressWarnings("static-method")
    protected int getBatchSize(final JdbcSettings jdbcSettings) {
        return ((jdbcSettings == null) || (jdbcSettings.getBatchSize() < 0)) ? JdbcSettings.DEFAULT_BATCH_SIZE : jdbcSettings.getBatchSize();
    }

    /**
     * Check statement setter.
     *
     * @param parsedSql
     * @param statementSetter
     * @return
     */
    @SuppressWarnings({ "static-method", "unused" })
    protected StatementSetter checkStatementSetter(final ParsedSql parsedSql, StatementSetter statementSetter) {
        if (statementSetter == null) {
            statementSetter = StatementSetter.DEFAULT;
        }

        return statementSetter;
    }

    /**
     * Check jdbc settings.
     *
     * @param jdbcSettings
     * @param parsedSql
     * @param attrs
     * @return
     */
    protected JdbcSettings checkJdbcSettings(final JdbcSettings jdbcSettings, final ParsedSql parsedSql, final Map<String, String> attrs) {
        JdbcSettings newJdbcSettings = null;

        if (jdbcSettings == null) {
            newJdbcSettings = setJdbcSettingsForParsedSql(_jdbcSettings, parsedSql, attrs);
        } else {
            newJdbcSettings = setJdbcSettingsForParsedSql(jdbcSettings, parsedSql, attrs);
        }

        return newJdbcSettings;
    }

    /**
     * Sets the jdbc settings for named SQL.
     *
     * @param jdbcSettings
     * @param parsedSql
     * @param attrs
     * @return
     */
    @SuppressWarnings("static-method")
    protected JdbcSettings setJdbcSettingsForParsedSql(JdbcSettings jdbcSettings, final ParsedSql parsedSql, final Map<String, String> attrs) {
        if ((parsedSql == null) || N.isNullOrEmpty(attrs)) {
            return jdbcSettings;
        } else {
            jdbcSettings = jdbcSettings.copy();

            String attr = attrs.get(SQLMapper.BATCH_SIZE);
            if (attr != null) {
                jdbcSettings.setBatchSize(Numbers.toInt(attr));
            }

            attr = attrs.get(SQLMapper.FETCH_SIZE);
            if (attr != null) {
                jdbcSettings.setFetchSize(Numbers.toInt(attr));
            }

            attr = attrs.get(SQLMapper.RESULT_SET_TYPE);
            if (attr != null) {
                Integer resultSetType = SQLMapper.RESULT_SET_TYPE_MAP.get(attr);

                if (resultSetType == null) {
                    throw new IllegalArgumentException("Result set type: '" + attr + "' is not supported");
                }

                jdbcSettings.setResultSetType(resultSetType);
            }

            attr = attrs.get(SQLMapper.TIMEOUT);
            if (attr != null) {
                jdbcSettings.setQueryTimeout(Numbers.toInt(attr));
            }

            return jdbcSettings;
        }
    }

    /**
     * Gets the named SQL.
     *
     * @param sql
     * @return
     */
    protected ParsedSql getParsedSql(final String sql) {
        N.checkArgNotNull(sql, "sql");

        ParsedSql parsedSql = null;

        if (_sqlMapper != null) {
            parsedSql = _sqlMapper.get(sql);
        }

        if (parsedSql == null) {
            parsedSql = ParsedSql.parse(sql);
        }

        return parsedSql;
    }

    /**
     * Gets the column label list.
     *
     * @param sql should be prepared sql because it will be cached.
     * @param rs
     * @return
     * @throws SQLException the SQL exception
     */
    protected static ImmutableList<String> getColumnLabelList(final String sql, final ResultSet rs) throws SQLException {
        ImmutableList<String> labelList = N.notNullOrEmpty(sql) ? _sqlColumnLabelPool.get(sql) : null;

        if (labelList == null) {
            labelList = ImmutableList.of(JdbcUtil.getColumnLabelList(rs));

            if (N.notNullOrEmpty(sql) && sql.length() <= CACHED_SQL_LENGTH) {
                if (_sqlColumnLabelPool.size() >= SQL_CACHE_SIZE) {
                    final List<String> tmp = new ArrayList<>(_sqlColumnLabelPool.keySet());
                    Maps.removeKeys(_sqlColumnLabelPool, tmp.subList(0, (int) (tmp.size() * 0.25)));
                }

                _sqlColumnLabelPool.put(sql, labelList);
            }
        }

        return labelList;
    }

    /**
     * Refer to http://landawn.com/introduction-to-jdbc.html about how to set parameters in <code>java.sql.PreparedStatement</code>
     *
     * @author Haiyang Li
     *
     */
    public interface StatementSetter extends Throwables.TriConsumer<ParsedSql, PreparedStatement, Object[], SQLException> {

        StatementSetter DEFAULT = JdbcUtil::setParameters;

        /**
         * Sets the parameters.
         *
         * @param parsedSql
         * @param stmt
         * @param parameters
         * @throws SQLException the SQL exception
         */
        @Override
        void accept(final ParsedSql parsedSql, final PreparedStatement stmt, final Object[] parameters) throws SQLException;

        static StatementSetter create(Throwables.Consumer<PreparedStatement, SQLException> stmtSetter) {
            return (parsedSql, stmt, parameters) -> stmtSetter.accept(stmt);
        }

        static StatementSetter create(Throwables.BiConsumer<NamedQuery, Object[], SQLException> stmtSetter) {
            return (parsedSql, stmt, parameters) -> stmtSetter.accept(new NamedQuery(stmt, parsedSql), parameters);
        }
    }
}
